Carlos A. S. Moretti
Carlos A. S. Moretti

Reputation: 131

Avoiding nesting subscriptions in Angular 2+?

I have 2 endpoints:

Actually I use:

this.user
  .subscribe((e) => {
     this.grants.get(e)
        .subscribe((x) => {
            console.log(x)
         })
  })

But this is an anti-pattern of Angular 2+/RxJS.

I would like to know how to do this following Angular/RxJS best practices.

Thanks

Upvotes: 10

Views: 4276

Answers (3)

Barremian
Barremian

Reputation: 31105

Avoiding nested subscriptions depends on the nature of the observables and how they depend on each other:

Co-dependent observables

When an observable (this.grants.get()) depends on the notification from another observable (this.user), you could use any of the RxJS higher order mapping operators switchMap, mergeMap, concatMap and exhaustMap. Each has their own purpose. You could find the differences between them here.

Brief differences b/n them

  • switchMap - cancel inner observable if the outer observable emits
  • mergeMap - trigger inner observable for each outer notification (flatten the outer notifications)
  • concatMap - essentially mergeMap with single concurrent request at any time (flattens the outer notifications but emit them in order)
  • exhaustMap - ignore outer notifications if inner observable hasn't completed

Illustration using switchMap operator

this.user.pipe(
  switchMap(e => this.grants.get(e))
).subscribe((x) => {
  console.log(x)
});

Independent observables

If the observables are independent of each other, you could use RxJS functions like forkJoin, combineLatest or zip to trigger the observables in parallel.

Brief differences b/n them

  • forkJoinα - emit only when all the observables complete
  • combineLatestα,β - emit when any of the observables emit (observables w/o emissions will emit old value)
  • zipα,β - emit when all of the observables emit

Illustration using forkJoin

forkJoin(this.obs1, this.obs2, ...).subscribe(
  res => console.log(res)
);

α - emits an array of notifications from each observable (eg. (this.obs1, this.obs2, ...) will emit ['res from obs1', 'res from obs2', ...]).

β - all observables should emit atleast once for the operator to emit

Upvotes: 16

Carlos A. S. Moretti
Carlos A. S. Moretti

Reputation: 131

Thank you guys. Its working with switchMap.

import { Component, VERSION, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { switchMap } from 'rxjs/operators';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
  name = 'Angular ' + VERSION.major ;

  constructor(private http: HttpClient) {
  }

  ngOnInit() {
    this.http.get("https://pokeapi.co/api/v2/pokemon?limit=100&offset=200")
      .pipe(
        switchMap((mp) => {
          console.log("Requisição 01", mp);
          return this.http.get("https://pokeapi.co/api/v2");
        }),
        switchMap((it) => {
          console.log("Requisição 02", it);
          return this.http.get("https://pokeapi.co/api/v2/pokemon/206/");
        })
      )
      .subscribe((d) => console.log("subscribe", d))
  }
}

Upvotes: 0

max meijer
max meijer

Reputation: 69

In your code it looks like they have no dependency on each other, because the result from this.user is not used in this.grants.getbut I'll ignore that for now.

You can use the await keyword to prevent nesting.

For example you could do the following:

const user  = await this.user.toPromise();
const grants = await this.grants.get().toPromise();

Upvotes: 0

Related Questions