Cory Rylan

My name is , Google Developer Expert, Speaker, Software Developer. Building Design Systems and Web Components.

Follow @coryrylan
Angular

Angular Async Data Binding with ngIf and ngElse

Cory Rylan

- 4 minutes

Updated

This article has been updated to the latest version Angular 17 and tested with Angular 16. The content is likely still applicable for all Angular 2 + versions.

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!

View Demo Code   
Twitter Facebook LinkedIn Email
 

No spam. Short occasional updates on Web Development articles, videos, and new courses in your inbox.

Related Posts

Angular

Creating Dynamic Tables in Angular

Learn how to easily create HTML tables in Angular from dynamic data sources.

Read Article
Web Components

Reusable Component Patterns - Default Slots

Learn about how to use default slots in Web Components for a more flexible API design.

Read Article
Web Components

Reusable Component Anti-Patterns - Semantic Obfuscation

Learn about UI Component API design and one of the common anti-patterns, Semantic Obfuscation.

Read Article