Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Safe Navigation Operator, RxJS, and Async Pipe Tinkering

DZone's Guide to

Safe Navigation Operator, RxJS, and Async Pipe Tinkering

RxJS? Safe Navigation operator? Async pipes? Not sure what I'm talking about? In this article I'd like to explore some cool combination of Http, RxJS and Async Pipes.

· Web Dev Zone
Free Resource

Get deep insight into Node.js applications with real-time metrics, CPU profiling, and heap snapshots with N|Solid from NodeSource. Learn more.

If you already played with Angular, I’m pretty sure you came across RxJS. It plays a big role in Angular, especially in Http, Forms, Async Pipes, Routing and also in application architecture patterns like ngrx/store.

Rob Wormald (Angular Developer Evangelist @ Google) showed some impressive usage of RxJS with Angular during his talk at NgEurope on “Angular & RxJS.” Some involved using the Safe Navigation Operator and how it can be replaced via async pipes.

Our task

Let’s assume we have the following data returned by some server API.

{
  "name": "Juri Strumpflohner",
  "status": "Currently coding on a screencast",
  "website": {
    "url": "http://juristr.com",
    "name": "juristr.com"
  },
  "twitter": {
    "url": "https://twitter.com/juristr",
    "name": "@juristr"
  }
}


We want to create a component that displays the details of this person by using the data it gets passed. Something like this:

import {Component, Input} from '@angular/core'

@Component({
  selector: 'person-detail',
  template: `
    <div>
      Name: {{ person.name }}<br/>
      Twitter: {{ person.twitter.name }}
    </div>
  `,
})
export class PeopleComponent {
  @Input() person;
}

Option 1: Pass in a Person Object

So our first option is to simply get the data in our parent component via the Angular httpservice.

this.http
      .get('person.json')
      .map(res => res.json())
      .subscribe(data => {
        this.person = data
      });


And then pass the person into our <person-detail> component.

import { Subject } from 'rxjs/Subscription';

@Component({
  selector: 'my-app',
  template: `
    <div>
      <person-detail [person]="person"></person-detail>
    </div>
  `,
})
export class App implements OnInit, OnDestroy {
  subscription: Subscription;
  person;

  constructor(private http:Http) { }

  ngOnInit() {
    this.subscription = 
            this.http
                  .get('person.json')
                  .map(res => res.json())
                  .subscribe(data => {
                    this.person = data
                  });
  }

  ngOnDestroy() {
    // unsubscribe to avoid memory leaks
    this.subscription.unsubscribe();
  }
}


But wait. This won’t work. Look at our PersonDetailComponent template:

<div>
      Name: {{ person.name }}<br/>
      Twitter: {{ person.twitter.name }}
</div>


We access name and twitter.name on person, however the latter won’t be defined when our component is instantiated. After all, we first need to call the server to get the data. So this results in a runtime exception.

Error: Uncaught (in promise): Error: Error in ./PersonDetailComponent class PersonDetailComponent - inline template:1:9 caused by: Cannot read property 'name' of undefined
TypeError: Cannot read property 'name' of undefined
    at _View_PersonDetailComponent0.detectChangesInternal (VM3423 component.ngfactory.js:45)
    at _View_PersonDetailComponent0.AppView.detectChanges (core.umd.js:9305)
    at _View_PersonDetailComponent0.DebugAppView.detectChanges (core.umd.js:9410)
    at _View_App0.AppView.detectViewChildrenChanges (core.umd.js:9331)
    ...


Safe Navigation Operator to the Rescue

By using the Safe Navigation Operator (?) we can change our PersonDetailComponenttemplate to this:

<div>
      Name: {{ person?.name }}<br/>
      Twitter: {{ person?.twitter.name }}
</div>


Simply speaking, this special operator allows us to bind data to our template that will be available later. When the data becomes available, values get evaluated and rebounded via change tracking.

Option 2: Using Async Pipes

First of all, Pipes are what you may know as “filters” from Angular 1.x. Just like the original Unix pipes, they allow you to pass data through it and do something with it, such as transform the data for instance. Here’s an example of how a pipe that formats a date value could look like:

{{ someDateValue | format: 'dd/MM/yyyy' }}


There’s a special, built-in pipe, called async. The async pipe accepts an RxJS Observable object and does the entire subscription and handling for us. So we can basically transform our original example to this:

@Component(...)
export class App {
  person;

  constructor(private http:Http) { }

  ngOnInit() {
    this.person = this.http
      .get('person.json')
      .map(res => res.json());
  }
}


Note how there is no more subscribe(...) part but instead, we directly assign the returned Observable to our person variable. But who does the subscription then? It’s the async pipe.

Variant 1: Async Pipe in the Detail Component

Our parent component (or smart component) remains unchanged, while our detail (or dumb component) displaying the person must be changed. Given the passed @Inputperson is an Observablewe need to wrap it with the async pipe: (person | async)?.name.

@Component({
  selector: 'person-detail',
  template: `
    <div>
      Name: <br/>
      Twitter: 
    </div>
  `,
})
export class PersonDetailComponent { 
    @Input() person;
    ...
}


Variant 2: Async Pipe in the Parent Component

I’m not that big of a fan of the previous variant, where our dumb component visualizing the detail of our person needs to know about the async nature of its input. That creates coupling to the outside world. Within our dumb component, I don’t want to know where my data comes from; its responsibility is mainly to visualize the input.

So we can do better. Rather than using the async pipe in our dumb component, let’s move it out to our parent.

@Component({
  selector: 'my-app',
  template: `
    <div>
      <person-detail [person]="person | async"></person-detail>
    </div>
  `,
})
export class App { ... }


Our dumb component’s template can be left without the async wrapper.

This is much nicer in my opinion. Also, note that a big advantage of using the built-in async pipe is that we don’t have to deal with the Observable subscription/unsubscription by ourselves anymore.

Safe Navigation Operator vs. Default Values

There’s one thing left which we would change as well. Our dumb component still uses the “safe navigation operator”:

import {Component, NgModule, Input} from '@angular/core'

@Component({
  selector: 'person-detail',
  template: `
    <div>
      Name: {{ person?.name }}<br/>
      Twitter: {{ person?.twitter.name }}
    </div>
  `,
})
export class PersonDetailComponent {

  @Input() person;

}


Obviously, we can totally live with that, but there are other options as well by setting some default values on our @Input. Let’s explore.

Version 2:

Don’t use the safe navigation operator, but rather do some default initialization of your @Input object.

import {Component, NgModule, Input} from '@angular/core'

@Component({
  selector: 'person-detail',
  template: `
    <div>
      Name: {{ person.name }}<br/>
      Twitter: {{ person.twitter.name }}
    </div>
  `,
})
export class PersonDetailComponent {

  @Input() person;

  ngOnInit() {
    // set default init -> MUST BE IN ngOnInit
     this.person = { twitter: {} };
  }
}


Note, for some (to me) unknown reason, this has to be done in the ngOnInit lifecycle event, otherwise, it doesn’t work.

Option 3: Lists

Async pipes work even more nicely. Our detail component gets the data and uses neither the safe navigation operator nor default values. The reason is that our *ngFor serves as a guard until the data arrives.

import {Component, NgModule, Input} from '@angular/core'

@Component({
  selector: 'my-people',
  template: `
    <div *ngFor="let person of people">
      {{ person.twitter.name }}
    </div>
  `,
})
export class PeopleComponent {

  @Input() people;

  constructor() {

  }
}


Conclusion

The async pipe is a really powerful operator.

  • It deeply integrates with RxJS Observables and the Angular change detection mechanism.
  • It handles Observable subscriptions transparently for us (in an optimized way).
  • It makes our async code look as if was synchronous.

Don’t forget to check out Rob’s talk.

Node.js application metrics sent directly to any statsd-compliant system. Get N|Solid

Topics:
web dev ,rxjs ,http

Published at DZone with permission of Juri Strumpflohner. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}