LocalStorage within Angular Application not working as intended - javascript

In my Angular application. I have an array which is getting objects pushed to it from an rest api. The array is called playlist=[] and it is being shared across the components with a service called playlist service. Also within this service are two functions. One to save the playlist to localStorage and one to get it from localStorage. So what is happening is when I save the playlist, it saves to local storage fine. Even if I refresh they still are in the localstorage. So when I use my app in a single session (not refreshing the browser) the savePlaylist() method adds objects to the playlist array, it does not overwrite them (So that is fine). However if I refresh the page add items and then save - the items are overwritten, when they should be added to the localstorage ones that are already there saved. Is this possible? Is this to do with sessionStorage? Any Ideas? My code so far is:
playlist.service.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class PlaylistService {
public playlist = [];
getPlaylist() {
if (localStorage.getItem('playlist') == null) {
this.playlist = [];
} else {
this.playlist = JSON.parse(localStorage.getItem('playlist'));
}
}
savePlaylist() {
// first save the data
localStorage.setItem('playlist', JSON.stringify(this.playlist));
// get what is saved afterwords
this.playlist = JSON.parse(localStorage.getItem('playlist'));
console.log('Saved', this.playlist);
}
constructor() {
}
}
playlist.ts (Where it should show the save playlist)
import { Component, OnInit } from '#angular/core';
import { PlaylistService } from '../../../services/playlist.service';
import { faSave } from '#fortawesome/free-solid-svg-icons';
#Component({
selector: 'app-playlist-view',
templateUrl: './playlist-view.component.html',
styleUrls: ['./playlist-view.component.scss']
})
export class PlaylistViewComponent implements OnInit {
faSave = faSave;
constructor(private list: PlaylistService) { }
ngOnInit() {
this.list.getPlaylist();
}
}
playlist.html
<app-header></app-header>
<div class="container">
<table class="table mt-3 mb-3">
<thead class="thead-light">
<tr>
<th>Artwork</th>
<th>Artist</th>
<th>Title</th>
<th>Genre</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of list.playlist">
<td><img src="{{user.artworkUrl60}}"></td>
<td>{{user.artistName}}</td>
<td>{{user.collectionName}}</td>
<td>{{user.primaryGenreName}}</td>
<td>{{user.collectionPrice}}</td>
</tr>
</tbody>
</table>
</div>
<app-footer></app-footer>

Related

How do I send REST response to html in angular?

I am able to get data from the REST API in my application, i am able to print the data on to the console, but no idea how do i display on to html, can any one help me on this please?
App.component.ts
import { HttpClient } from '#angular/common/http';
import { Component, Inject } from '#angular/core';
import { Employee } from './employee';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'emp-data-example';
employees:Employee[]=[];
constructor(#Inject(HttpClient) private http:HttpClient){
}
ngOnInit(){
this.http.get("http://localhost:8080/api/all").subscribe(
response=>{
this.employees=response; // here is error because response is an Object and employees is an array
});
//resp.subscribe((data)=>{this.employees=data});
}
}
app.component.html
<div>
<table border="1">
<tr>
<th>Emp Id</th>
<th>Emp Name</th>
<th>Salary</th>
</tr>
<tr ngFor="let employee of employees">
<td>{{employee.id}}</td>
<td>{{employee.name}}</td>
<td>{{employee.department}}</td>
<td>{{employee.salary}}</td>
</tr>
</table>
</div>
Try to set the type of the response to Employee and add it to your array like so:
this.http.get("http://localhost:8080/api/all").subscribe(
(response: Employee[]) =>{
this.employees.push(...response);
});
since you are getting array in the response and its from an employee type it should be something like that:
this.http.get("http://localhost:8080/api/all").subscribe(
(response: Array<Employee>) =>{
this.employees = response;
});

Some times data is not populating into view (ngFor) through array push in angular 8

I have a simple data which I am populating through ngFor into view. I am getting all the objects and pushing into array and then from there I am populating into html.Here its working fine.But in my project the scenario is like I need to get into this page by selecting a event from a previous page.When I am frequently going and selecting events and coming to this page, some cases my data pushing into array but not populating into view.Is there any solution for this.Here is the code below
home.component.html
<div>
<table>
<tr *ngFor="let x of groupList">
<td ><span>{{x.vehicle_number}}</span></td>
<td ><span>{{x.vehicle_name}}</span></td>
<td ><span>{{x.status}}</span></td>
</tr>
</table>
</div>
home.component.html
import { Component, OnInit } from '#angular/core';
import { CurrencyPipe } from './../pipes/currency.pipe';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
imageSource :any;
statusdata1: any;
moreThanTen:boolean = false;
showit:boolean = false;
groupList:any = [];
constructor() {}
ngOnInit() {
/* First data */
let response =
{"vehicle_number":1,"vehicle_name":"car","status":"yellow"}
let response1 = {"vehicle_number":0,"vehicle_name":"car","status":"yellow"}
let response2 = {"vehicle_number":2,"vehicle_name":"car","status":"yellow"}
this.groupList.push(response,response1,response2);
console.log(this.groupList);
}
}
Please try to implement onPush or ChangeDetectionStrategy in your component
Doing this will instruct Angular to run change detection on these components and their sub-tree only when new references are passed to them versus when data is simply mutated.
Run this.ref.markForCheck() or this.ref.detectChanges() when you update your variable and want it to reflect in html
Please check the following links for more information
https://angular.io/api/core/ChangeDetectionStrategy
https://alligator.io/angular/change-detection-strategy/

Angular View Component Data Loss during Reload Refresh using BehaviorSubject

I am writing an Angular9 app with a use case where I am buffering data across 3 pages (with forms taking data input).
Then, on the 4th page, I display a summary of the details gathered across the 3 pages as read only text.
I have implemented the data buffering using the following article:
https://www.infragistics.com/community/blogs/b/infragistics/posts/simplest-way-to-share-data-between-two-unrelated-components-in-angular
Which uses the BehaviorSubject
Below is my data service logic:
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable, BehaviorSubject, throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
import { UserResponse } from './user-response';
#Injectable({
providedIn: 'root'
})
export class UserService {
//Details Page 1
public salutation: BehaviorSubject<string>;
public firstName: BehaviorSubject<string>;
// Base url
baseurl = 'https://localhost:8080/rest';
constructor(private http: HttpClient) { }
// Http Headers
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
}
// POST
createUser(data): Observable<UserResponse> {
alert('user - details = '+ data);
return this.http.post<UserResponse>(this.baseurl + '/user', JSON.stringify(data), this.httpOptions)
.pipe(
retry(1),
catchError(this.errorHandler)
)
}
// Error handling
errorHandler(error) {
let errorMessage = '';
if(error.error instanceof ErrorEvent) {
// Get client-side error
errorMessage = error.error.message;
} else {
// Get server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
console.log(errorMessage);
return throwError(errorMessage);
}
}
Below is my first view component:
import { Component, OnInit } from '#angular/core';
import { NgForm } from '#angular/forms';
import { UserService } from '../service/user.service';
import { BehaviorSubject } from 'rxjs';
#Component({
selector: 'app-details1',
templateUrl: './details1.component.html',
styleUrls: ['./details1.component.css']
})
export class Details1Component implements OnInit {
salutation: string;
firstName: string;
constructor(private userService: UserService) { }
ngOnInit(): void {
}
goToDetails2(form : NgForm) {
this.userService.salutation = new BehaviorSubject(form.value.salutation);
this.userService.firstName = new BehaviorSubject(form.value.firstName);
}
}
Below is a snippet of my final/summary view component
import { Component, OnInit} from '#angular/core';
import { Router } from '#angular/router';
import { UserService } from '../service/user.service';
#Component({
selector: 'app-summary',
templateUrl: './summary.component.html',
styleUrls: ['./summary.component.css']
})
export class SummaryComponent implements OnInit {
salutation: string;
firstName: string;
constructor(
private router: Router,
public userService: UserService
) {
}
ngOnInit(): void {
this.userService.salutation.subscribe(c => { this.salutation = c; });
this.userService.firstName.subscribe(c => { this.firstName = c; });
}
}
On the final page my html excerpts is as follows:
<form #detailsForm4="ngForm">
<div class="govuk-form-group">
<table class="govuk-table">
<thead class="govuk-table__head">
<tr class="govuk-table__row"></tr>
</thead>
<tbody class="govuk-table__body" align="left">
<tr class="govuk-table__row">
<th scope="row" class="govuk-table__header">Salutation</th>
<td class="govuk-table__cell"> {{salutation}} </td>
<td class="govuk-table__cell"> </td>
</tr>
<tr class="govuk-table__row">
<th scope="row" class="govuk-table__header">First name</th>
<td class="govuk-table__cell"> {{firstName}} </td>
<td class="govuk-table__cell"> </td>
</tr>
The data is displayed on the summary page as captured on the forms in pages 1-3,
BUT the data is lost on page refresh or when my machine hibernates etc
I have read about local and session storage persistence and used it since the angularjs 1.x days.
Ages ago, I was actually surprised when the data disappeared on my summary screen!
I guess that an ideal solution will involve storing the data in client side storage when the user navigates away from the relevant form, then retrieving the data on the summary page.
I guess, when the summary page is refreshed or displayed for the first time,
I will check storage for the data and display if it exist.
If storage is empty, then I will be using data from previous page at runtime.
Please, I need help with a best practice and stable solution, which is cross browser friendly and intelligent for a commercial application.
Also, an approach that fits into Angular9 BehaviorSubject - rxjs stack OR Any recent features, because I know Angular has evolved since AngularJs 1.x ?
The summary details is only changed when the user navigates back to any of the pages 1-3 to change the form details.
I will appreciate any code snippet or reference to keep the data on the summary page.
Thanks a lot.
I will propose you to use localStorage within your SummaryComponent within ngOnDestroy lifecycle hook. It will always set your data within localStorage before component will be destroyed due to refresh. Then when you are trying to retrieve data from the service, if it will be empty try to get it from localStorage.
export class SummaryComponent implements OnInit, OnDestroy {
salutation: string;
firstName: string;
constructor(
private router: Router,
public userService: UserService
) {
}
ngOnInit(): void {
this.userService.salutation.subscribe(c => { this.salutation = c || localStorage.get('salutation'); });
this.userService.firstName.subscribe(c => { this.firstName = c || localStorage.get('firstName'); });
}
ngOnDestroy(): void {
localStorage.setItem('salutation', this.salutation));
localStorage.setItem('firstName', this.firstName));
}
}
Similar implementation you can do in the component where this values are set and you expect that user can refresh the page before go to the next page.
EDIT
Sorry, you are right that ngOnDestory will not trigger on page refresh to do that you will need to handle window:beforeunload.
#HostListener('window:beforeunload', ['$event'])
beforeunload($event: Event): void {
// set localStorage here
}
Going forward with your solution, the best option to set items to the localStorage is the place where you set values to the service. It's probably goToDetails2 method.
goToDetails2(form : NgForm) {
this.userService.salutation = new BehaviorSubject(form.value.salutation);
this.userService.firstName = new BehaviorSubject(form.value.firstName);
localStorage.setItem('salutation', form.value.salutation));
localStorage.setItem('firstName', form.value.firstName));
}
EDIT-2 to your problem described in the comment
I will propose you to initialize your BehaviorSubject directly inside of the service.
#Injectable({
providedIn: 'root'
})
export class UserService {
//Details Page 1
public salutation: BehaviorSubject<string> = new BehaviorSubject('');
public firstName: BehaviorSubject<string> = new BehaviorSubject('');
And then within your Details1Component set values on them using next
goToDetails2(form : NgForm) {
this.userService.salutation.next(form.value.salutation);
this.userService.firstName.next(form.value.firstName);
localStorage.setItem('salutation', form.value.salutation));
localStorage.setItem('firstName', form.value.firstName));
}

Syncing (or pre-populating) Firebase data into Angular 5 FormArrays?

Using Angular 5, AngularFire2, and Firestore, I have a set of items that I want listed out in a grid that will be editable from the grid itself.
In the template, the grid is iterating over a FormArray inside a FormGroup.
<form [formGroup]="form">
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Serial Number</th>
<th></th>
</tr>
</thead>
<tbody formArrayName="devices">
<tr *ngFor="let device of form.controls.devices.controls; let i = index" [formGroupName]="i">
<th scope="row"><input type="text" class="form-control" formControlName="serialNumber"></th>
<td><button type="button" class="btn-sm btn-danger" (click)="deleteDevice(i)">Delete</button></td>
</tr>
</tbody>
</table>
</form>
In my Angular component, I'm calling buildForm() inside ngOnInit() to initially generate the form to give the template something to work with. If I don't do this, I get errors in the console.
I'm then generating the form again in the collection's snapshotChanges() in order to make sure the data is synced properly between Firebase and the FormArray.
If I don't do this, I'll get weird behavior such as rows remaining in the grid even after the item has been deleted or extra rows appearing once I add in another item.
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
devicesCol: any;
devices: any;
serialNumber: string;
form: any;
constructor(private afs: AngularFirestore, private fb: FormBuilder) { }
ngOnInit() {
this.buildForm(); // Generate the form initially to give the template something to work with
this.devicesCol = this.afs.collection('devices', ref => ref.orderBy('deviceName'));
this.devicesCol.snapshotChanges().map(actions => {
return actions.map(a => {
const id = a.payload.doc.id;
const data = a.payload.doc.data();
return { id, data };
});
}).subscribe(result => {
this.form = this.fb.group({
devices: this.fb.array([]);
});
var devicesForm = this.form.get('devices');
this.devices.forEach((element) => {
devicesForm.push(this.fb.group({ 'id': element.id, 'serialNumber': element.data.serialNumber, 'deviceName': element.data.deviceName }));
}
});
}
buildForm() {
this.form = this.fb.group({
devices: this.fb.array([]);
})
}
addDevice() {
this.afs.collection('devices').add({ 'serialNumber': this.serialNumber, 'deviceName': '' }); // Add to firebase
}
deleteDevice(id) {
var devices = this.form.get('devices');
var deviceID = devices.at(id).get('id').value;
this.afs.doc('devices/' + deviceID).delete(); // Remove from firebase
devices.removeAt(id); // Remove from FormArray (probably unnecessary since the form will be rebuilt in snapshotChanges()
}
}
While this does work, I'm not sure it's best way to go about it. Is there a better way to sync data between Firebase and a FormArray?

#Input() Not Passing As Expected Between Parent-Child Components in Angular 2 App

I am trying to abstract out a tabular-data display to make it a child component that can be loaded into various parent components. I'm doing this to make the overall app "dryer". Before I was using an observable to subscribe to a service and make API calls and then printing directly to each component view (each of which had the tabular layout). Now I want to make the tabular data area a child component, and just bind the results of the observable for each of the parent components. For whatever reason, this is not working as expected.
Here is what I have in the parent component view:
<div class="page-view">
<div class="page-view-left">
<admin-left-panel></admin-left-panel>
</div>
<div class="page-view-right">
<div class="page-content">
<admin-tabs></admin-tabs>
<table-display [records]="records"></table-display>
</div>
</div>
</div>
And the component file looks like this:
import { API } from './../../../data/api.service';
import { AccountService } from './../../../data/account.service';
import { Component, OnInit, Input } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { TableDisplayComponent } from './../table-display/table-display.component';
#Component({
selector: 'account-comp',
templateUrl: 'app/views/account/account.component.html',
styleUrls: ['app/styles/app.styles.css']
})
export class AccountComponent extends TabPage implements OnInit {
private section: string;
records = [];
errorMsg: string;
constructor(private accountService: AccountService,
router: Router,
route: ActivatedRoute) {
}
ngOnInit() {
this.accountService.getAccount()
.subscribe(resRecordsData => this.records = resRecordsData,
responseRecordsError => this.errorMsg = responseRecordsError);
}
}
Then, in the child component (the one that contains the table-display view), I am including an #Input() for "records" - which is what the result of my observable is assigned to in the parent component. So in the child (table-display) component, I have this:
import { AccountService } from './../../../data/account.service';
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'table-display',
templateUrl: './table-display.component.html',
styleUrls: ['./table-display.component.less']
})
export class TableDisplayComponent {
#Input() records;
constructor() {
}
}
Lastly, here's some of the relevant code from my table-display view:
<tr *ngFor="let record of records; let i = index;">
<td>{{record.name.first}} {{record.name.last}}</td>
<td>{{record.startDate | date:"MM/dd/yy"}}</td>
<td><a class="bluelink" [routerLink]="['/client', record._id ]">{{record.name.first}} {{record.name.last}}</a></td>
When I use it with this configuration, I get "undefined" errors for the "records" properties I'm pulling in via the API/database. I wasn't getting these errors when I had both the table display and the service call within the same component. So all I've done here is abstract out the table-display so I can use it nested within several parent components, rather than having that same table-display show up in full in every parent component that needs it.
What am I missing here? What looks wrong in this configuration?
You need to protect against record being null until it comes in to your child component (and therefore it's view).
Use Elvis operators to protect your template:
<tr *ngFor="let record of records; let i = index;">
<td>{{record?.name?.first}} {{record?.name?.last}}</td>
<td>{{record?.startDate | date:"MM/dd/yy"}}</td>
<td><a class="bluelink" [routerLink]="['/client', record?._id ]"> {{record?.name?.first}} {{record?.name?.last}}</a></td>
You can also assign your input to an empty array to help with this issue:
#Input() records = [];

Categories