Check out myAngular article series with live demos and Facebook group Angular - Advanced Topics

How to Subscribe Less in RxJs

Torgeir "Tor" Helgevold
- JavaScript Developer and Blogger
Published: Wed Aug 02 2017

In this post I will show how to avoid subscriptions in RxJs by utilizing the async pipe.

Lately I have seen more and more people discourage manual subscriptions to RxJs observables in Angular components. Instead the recommendation is to use the async pipe to handle subscribing and unsubscribing.

Initially I was a bit hesitant to use the async pipe since I assumed it would be less flexible to rely on the async pipe instead of a manual subscription.

Also, most of the time when I see examples of how to use the async pipe, the examples are usually too trivial to reflect realistic scenarios.

Common scenarios like enabling/disabling spinners and error handling are often left out of the examples.

In the following example I will show how to use the async pipe with a relatively complex observable.

In addition to a complicated http sequence I am also managing a loading indicator and error handling.

Service

Let’s start with the http portion of the observable.

Despite the complexity of the observable with chaining and parallel requests, it’s pretty straightforward.

import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/forkJoin'; import 'rxjs/add/operator/mergeMap'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/delay'; @Injectable() export class LocationService { constructor(private http: HttpClient) {} getLocations() { return this .http .get('./assets/countries.json') .flatMap((data: any) => Observable .forkJoin(data.countries .map((country: string) => this .http .get(`./assets/${country}.json`) .map((locations: any) => {return {country: country.toUpperCase(), cities: locations.cities}}))) ); } }

Component

Let’s move on to the component.

Interesting questions are: How can we manage the spinner and add error handling?

import { Component } from '@angular/core'; import { LocationService } from './location.service'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/catch'; import 'rxjs/add/observable/of'; import { Observable } from 'rxjs/Observable'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { spinner: string; countries: Observable<Object[]>; error: string; constructor(private locationService: LocationService) {} ngOnInit() { this.spinner = 'Loading Data'; this.countries = this.locationService .getLocations() .do(() => { this.spinner = '' }) .catch(e => { return Observable.of([{error: 'There was an error'}]); }); } }

It turns out that the secret to doing processing like turning on and off loading indicators is adding a “do” clause to the observable.

This gives us a hook to set side effects that happen outside of the observable itself, but still depend on the state of the observable.

Error handling can be added using a simple catch block. Notice here that I am transforming the error to a valid response that can be displayed in the template.

The template is where I add the async pipe as seen below:

<h1>Loading Data</h1> {{spinner}} <div *ngFor="let c of countries | async"> <div *ngIf="!c.error; else error"> {{c.country}} <div class="city" *ngFor="let city of c.cities"> {{city}} </div> </div> </div> <ng-template #error>There was an error!</ng-template>

In the template I am relying on a conditional to render, either the successful response, or an error response.

There you have it, a simple way to avoid manual subscriptions.

If you liked this article, share it with your friends on social media:

We also have a new Facebook group about advanced Angular topics.

I invite you to follow me on twitter