Asynchronous programming
Angular Asynchronous Patterns RxJS Top Typescript Visual Studio Code

Using RxJS BehaviorSubject to Synchronize Data Loading in Angular Forms

Welcome to today’s post.

In this blog I will show how to use the RxJS observable BehaviorSubject to synchronize loading of data subscriptions from Web APIs into an Angular form template.

Before I explain what the BehaviorSubject observable is and how we will apply it to load data into our form, I will look at the reasons why we would synchronise data loads into forms.

In Angular applications that contain many components, there may be components that depend on other components. In this case, the need to coordinate incoming data from other parts of the application is important.

Explaining Data Dependency and Data Loading within Angular Components

The simplest case of a data dependency is a parent component, which may be a list of records, and child detail components that filter data based on a parameter obtained from a parent component. When the data input to the child component is a single parameter, such as an integer value, within the initialization event of the component we run a query that selects a single record from an API service, which we store within an internal data object. The data object is then presented within the HTML template of the component.   

The next case of data dependency is a component that has multiple data inputs, with each input parameter extracting data from difference sources. In this case, the initialization of the component requires the data object required for the component’s HTML template to be built from multiple data sources. The resulting object is then presented within the HTML template of the component.

What is the common requirement that the above two scenarios share?

They both require the rendering of HTML template of the component to know when to the data is ready to be presented.

What would happen if we were to simply pass in the input parameters, filter out the data into an object and reference the data object within our HTML template? The answer is that in many instances, the HTML template would not pick up the data object in a populated state. In this case, the output would not display any data. Would adding a delay loop just after the data object is initialised solve the problem? No. Because the HTML template part of our component will not wait for the data before rendering.

Where we solve the above conundrum is to make use of an asynchronous observable within the HTML template. I will show how this is done later.

I will first explain the use of the BehaviorSubject observable in the next section.

Explaining the BehaviorSubject Observable

The BehaviorSubject observable, when we subscribe to it will return the last value it has received. It requires an initial value and for this reason, is ideal for interfaces that change depending on receipt of asynchronous data. In addition, it is a Subject, which is a multicast observable which allows multiple subscribers to receive emitted data.

Without the adoption of synchronization, our data will be displayed before the incoming data from our Web API has had an opportunity to complete subscribing to the observable within our Angular form.

The most practical example is where we are loading data into a web form.

While waiting for the data we display an animated graphic (such as a spinner or progress bar) to the end user.

When the data is successfully loaded, the animated graphic disappears, and the data is displayed in a table or chart.

With the use of the BehaviorSubject observable in a UI, we can achieve the three goals:

  1. Assign a default value to our observable.
  2. To use an asynchronous observable as a flag to notify our form template that the data can be displayed.
  3. Automatically free the observable from the Angular form component once it is destroyed.

I will show how the above is achieved within an Angular component in the next section.

Solving the Data Loading Synchronization Problem in an Angular Component

To declare and initialize our observable in our component as shown:

import { BehaviorSubject } from 'rxjs';
import { ProgressSpinnerMode } from '@angular/material/progress-spinner';
import { delay } from 'rxjs/operators';

We declare an observable variable initialized to indicate the data has not loaded:

hasLoaded$ = new BehaviorSubject<boolean>(false);

In the form initialisation, we subscribe to the data.

When the data subscription has completed, the observable is set to allow the form to display the data:

ngOnInit() {
  this.api.getBooks().pipe(delay(3000)).subscribe(res => {
    this.hasLoaded$.next(true);
    this.books = res
    console.log("read books from API");
  })
}

In the above I have delayed the return of results from the API request to a few seconds so that we can view how the loading would resemble if the request took longer to return a response.

The line of code below sets the hasLoaded$ observable to the next value of true, which is then subscribed to by an asynchronous observable.

this.hasLoaded$.next(true);

In the HTML template below, the asynchronous observable waits for changes to the next value of the hasLoaded$ observable and either displays the spinner (data still loading) or displays the book data (data has loaded):

<mat-card>
  <mat-card-content>
    <div><b>List of Books</b></div>
    <div *ngIf="hasLoaded$ | async; else loading">
      <mat-list *ngFor="let book of books">
        <div style="color: blueviolet; 
background-color:aquamarine;">
          <mat-list-item 
            class="clickLink" 
            (click)="selectBookById(book.id)"> 
            {{book.title}} 
          </mat-list-item>
        </div>
      </mat-list>
    </div>
    <br />
    <ng-template #loading>  
      <div>
        <br />
        <mat-progress-spinner
          [color]="primary"
          [mode]="mode"
          [value]="value">
        </mat-progress-spinner>
        loading books .. please wait
      </div>
    </ng-template>
  </mat-card-content>
  <mat-card-actions>
  </mat-card-actions>
</mat-card>

In this example we include a short delay of a few seconds before we start subscribing to the web API data so that we can simulate a longer delay in receiving data. With a small data set, the spinner would not be noticeable, but with larger data sets a delay would not be necessary and the progress spinner would be visible.

As we can see, the use of BehaviourSubject observable acts like a synchronization operator, allowing end users a better experience when receiving data from a backend or external service. Without the use of synchronization, the user experience would not be pleasant.

Using a basic Angular template within the view with a material progress component achieves this UI effect. We could also have used a progress bar or an animated graphic, whichever suits your requirement.

That’s all for this post.

I hope you found it useful and informative.

Social media & sharing icons powered by UltimatelySocial