9

I have 2 endpoints:

  • 1 endpoint to get current user logged.
  • 1 endpoint to get grants of this user.

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

ruth
  • 29,535
  • 4
  • 30
  • 57

3 Answers3

15

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

ruth
  • 29,535
  • 4
  • 30
  • 57
0

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();
max meijer
  • 69
  • 5
  • `this.grants.get` does use the return value from `this.user`. Also, it isn't necessary to switch from the Observable paradigm to the Promise paradigm, moreover, it will break the thing if `user` emits more than once. – mbojko Sep 01 '20 at 10:40
  • There are multiple issues here: (1). Unnecessary switch from observables to promise. (2). OP's observables are co-dependent. (3). `toPromise()` method is [being deprecated](https://indepth.dev/rxjs-heads-up-topromise-is-being-deprecated/) in RxJS 7 and will be gone in RxJS 8. – ruth Sep 01 '20 at 10:42
0

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))
  }
}