Angular View Component Data Loss during Reload Refresh using BehaviorSubject

1 answer

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.

All answers to this question, which has the identifier 60840225

The best answer:

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));       } 

Last questions

how do i remove the switch on my home screen?
how to edit the JS date and time to update atuomatically?
How to utilize data stored in a multidimensional array
Powermockito not mocking URL constructor in URI.toURL() method
Android Bluetooth LE Scanner only scans when phone's Location is turned on in some devices
docker wordpress container can't connect to mysql container
How can I declare a number in java that is more than 64-bits? [duplicate]
Optaplanner solutionClass entityCollectionProperty should never return null error when simple JSON object passed to controller
Anylogic, get the time a pedestrain is in a queue
How do I fix this syntax issue with my .flex file?
Optimizing query in PHP
How to find the highest number of a column and print two columns of that row in R?
Ideas on “Error: Type com.google.firebase.iid.zzav is referenced as an interface from com.google.firebase.messaging.zzd”?
JCIFS SmbFile.exists() and SmbFile.isDirectory() return false when it exists and I can listFiles()
PHP total order
Laravel booking system design
neural net - undefined column selected
How to indicate y axis does not start from 0 in ggplot?
Fragments in backStack
Spinner how to change the data