Angular Async Data Binding with ngIf and ngElse
In this article, we are going to cover a new feature introduced in Angular. This feature is a special added syntax to the ngIf
statement to make it easier to bind async data to our Angular templates.
When building Angular applications, it's likely you are working with Observables (specifically RxJS) to handle asynchronous data. Typically we get this async data through Angular's Http service which returns an Observable with our data response. We will cover three different ways of data binding and the last being the new ngIf / ngElse feature.
Manual Subscription Management
In this first example, we are going to bind some user data to a component from an artificially created Observable that emulates a slow API connection instead of calling an API directly. We will also cover the various ways we can bind to an Observable.
import { Component } from '@angular/core';
import { Observable, Subscription, of } from 'rxjs';
import { delay, share } from 'rxjs/operators';
@Component({
selector: 'demo-app',
templateUrl: 'app/app.component.html'
})
export class AppComponent {
user: any;
subscription: Subscription;
constructor() {}
ngOnInit() {
// Manual subscription handling
this.subscription = this.getAsyncData().subscribe(u => (this.user = u));
}
ngOnDestroy() {
// Only need to unsubscribe if its a multi event Observable
this.subscription.unsubscribe();
}
getAsyncData() {
// Fake Slow Async Data
return of({
firstName: 'Luke',
lastName: 'Skywalker',
age: 65,
height: 172,
mass: 77,
homeworld: 'Tatooine'
}).pipe(delay(2000));
}
}
In this component, we have an Observable that emits some async data and using RxJS operators. We are causing the data to slow down by a couple of seconds to emulate a slow network connection. This code has nothing to do with our ngElse
feature just note we are binding a user property on our component as a plain JavaScript Object.
In this example, we subscribe to our Observable. Once we receive the data, we will assign the data to our user
property. Note we also must unsubscribe from our Observable when our component is destroyed. We must manually unsubscribe to any Observable that may emit multiple values. In our template, we can simply bind directly to our user
property.
<div>
<h2>{{user?.firstName}} {{user?.lastName}}</h2>
<dl>
<dt>Age:</dt>
<dd>{{user?.age}}</dd>
<dt>Height:</dt>
<dd>{{user?.height}}</dd>
<dt>Mass:</dt>
<dd>{{user?.mass}}</dd>
<dt>Homeworld:</dt>
<dd>{{user?.homeworld}}</dd>
</dl>
</div>
Note we must use the elvis operator ?
when accessing a property. This is because at initialization time the data does not exist causing our user to be undefined. The elvis operator allows us to lazily evaluate the properties of our object without throwing an error.
Async Pipe and the Share Operator
Another way to bind to async data in Angular is to use the async
pipe. With the Async pipe, we get the benefit of Angular auto-subscribing and unsubscribing with our Observables when the component is created and destroyed. To use the async
pipe, we bind our Observable directly to our component.
import { Component } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay, share } from 'rxjs/operators';
@Component({
selector: 'demo-app',
templateUrl: 'app/app.component.html'
})
export class AppComponent {
user: Observable<any>;
constructor() {}
ngOnInit() {
this.user = this.getAsyncData().pipe(share());
}
getAsyncData() {
// Fake Slow Async Data
return of({
firstName: 'Luke',
lastName: 'Skywalker',
age: 65,
height: 172,
mass: 77,
homeworld: 'Tatooine'
}).pipe(delay(2000));
}
}
Unfortunately, by itself, we must use the pipe on every property we want to access. This creates a subscription each time the async
pipe is used. To counter this we use the RxJS share()
operator to share the subscription with multiple subscribers preventing duplicate work.
<h2>Async Pipe and share()</h2>
<div>
<h2>{{(user | async)?.firstName}} {{(user | async)?.lastName}}</h2>
<dl>
<dt>Age:</dt>
<dd>{{(user | async)?.age}}</dd>
<dt>Height:</dt>
<dd>{{(user | async)?.height}}</dd>
<dt>Mass:</dt>
<dd>{{(user | async)?.mass}}</dd>
<dt>Home World:</dt>
<dd>{{(user | async)?.homeWorld}}</dd>
</dl>
</div>
As you can see this is verbose and not exactly terse. Nor does this template handle a loading message while the data loads into our template. Lets now look at the new ngIf
/ ngElse
feature.
NgIf and NgElse
We would like to be able to subscribe to our Async Pipe once and avoid the extra ceremony in our templates. Just like before, we bind our Observable directly to our component without subscribing nor handling unsubscribing. Let's take a look at the updated component template using the new ngElse feature.
<h1>With ngIf and ngElse</h1>
<div *ngIf="user$ | async as user; else loading">
<h2>{{user.firstName}} {{user.lastName}}</h2>
<dl>
<dt>Age:</dt>
<dd>{{user.age}}</dd>
<dt>Height:</dt>
<dd>{{user.height}}</dd>
<dt>Mass:</dt>
<dd>{{user.mass}}</dd>
<dt>Home World:</dt>
<dd>{{user.homeWorld}}</dd>
</dl>
</div>
<ng-template #loading>Loading User Data...</ng-template>
Let's break down what is happening in our template. The first line has several parts.
<div *ngIf="user$ | async as user; else loading"></div>
First, we are using a traditional *ngIf
in combination with the async
pipe to show our element if the user has loaded.
Next is the user$ | async as user
statement in the *ngIf
. Here we are creating a local template variable user
that Angular assigns the value from the Observable user$
. This allows us to interact directly with our user
Object without having to use the async
pipe over and over.
The last statement else loading
tells Angular if the condition is not met to show the loading template. The loading template is denoted using the ng-template
tag with a #loading
template reference name.
<ng-template #loading>Loading User Data...</ng-template>
Angular template tags are not rendered in the browser until needed. When the user is not loaded, Angular will show the loading template. Once loaded it will render the div element with the user content.
With this new syntax, we can reduce the need for the async pipe while having a nice feedback mechanism for when to show a loading indicator for users. Make sure to check out the demo below!