14

I have the following code which consists of multiple subscribes. What I need to achieve is like this :

  1. Subscribe to activatedRoute to get User and Product data.
  2. With the product data returned, subscribe to getSeller service by using the product data.
  3. Subscribe to getRating service by using the seller data returned.

My question : is there any better way to perform these nested subscription? is it a good practice to do like this?

this.activatedRoute.data.pipe(
 map((data) => {
    this.user = data['user'];
    this.product = data['product'];

    return this.product;
  })
).subscribe(result => {

  if (this.product === null) {
    this.router.navigate(['/home']);
  } else {
    this.displayCurrency = this.dataService.getCurrencySymbolById(this.product.currency);

    this.userService.getUser(this.product.createdBy).subscribe(seller => {
      this.seller = seller;

      this.ratingService.getRatingByUserId(seller.id).subscribe(rating => {
        this.rating = rating;
      })

    });
  }
});
Shashank Vivek
  • 16,888
  • 8
  • 62
  • 104
scholarwithfire
  • 345
  • 2
  • 8
  • 21
  • Possible duplicate of [RxJS subscribe inside subscribe with actions on error](https://stackoverflow.com/questions/54090015/rxjs-subscribe-inside-subscribe-with-actions-on-error) – GCSDC Apr 01 '19 at 03:41
  • Before that, just bear this in mind. You have to unsubscribe a subscription whenever it is not needed. In this case, the product creation and seller id subscriptions. Now your original problem, did you had a look at rxjs operators? If not, just go through them. You problem might be solved very easily by them. – V.i.K.i Apr 01 '19 at 03:43

3 Answers3

13

Technically, nesting subscribe works, but there is a more elegant and systematic way of handling this. You should really learn more about your RxJS operators.

First, we use mergeMap to map over the observable values from the activatedRoute into an inner observable.

Then, we use forkJoin to combine the observables into a single value observable, thus returning the value itself on the .subscribe()

this.activatedRoute.pipe(
    tap(data => console.log(data)),
    mergeMap(data => {
      if (data.product === null) {
        this.router.navigate(['/home']);
      } else {
        const getCurrency = this.dataService.getCurrencySymbolById(data.product.currency);
        const getUsers= this.userService.getUser(data.product.createdBy);
        const getRatings = this.ratingService.getRatingByUserId(seller.id)
        return forkJoin(getCurrency, getUsers, getRatings);
      }
    })
  ).subscribe(res => {
    console.log(res[0]); // currency
    console.log(res[1]); // user
    console.log(res[2]); // ratings

  }

EDIT: Turns out I have misread the original question, as getRatingsByUserId is dependent on getUser. Let me make some changes. Either ways, I will leave the code above as it is, as it is good for OP's reference.

this.activatedRoute.data.pipe(
  switchMap(data => {
    this.user = data['user'];
    this.product = data['product'];
    return this.userService.getUser(this.product.createdBy);
  }),
  switchMap(data => {
    if (this.product === null) {
      this.router.navigate(['/home']);
    } else {
      this.seller = seller;
      return this.userService.getRatingByUserId(this.product.createdBy); 
    }
  })
).subscribe(res => {
 console.log(res)
 // handle the rest
})
wentjun
  • 40,384
  • 10
  • 95
  • 107
  • If I read the question right, `seller` is read calling a `getUser` with `product.createdBy` as parameter. This means that `getUser` should be concatenated via `switchMap` With `getRatingsByUserId` – Picci Apr 01 '19 at 06:07
  • Oh, right, thanks for pointing that out..! I have misread that part – wentjun Apr 01 '19 at 06:14
1

Use switchMap to switch to a new observable.

this.activatedRoute.data.switchMap((routeData) => {
     this.user = routeData['user'];
     this.product = routeData['product'];
     return this.userService.getUser(this.product.createdBy);
    }).switchMap(seller => {
       this.seller = seller;
        return this.ratingService.getRatingByUserId(seller.id);
    }).subscribe(rating => {
      this.rating = rating;
    })
Syam Prasad
  • 149
  • 1
  • 1
  • 6
0

You can use mergeMap

import { of, from } from 'rxjs';
import { map, mergeMap, delay, mergeAll } from 'rxjs/operators';

const getData = (param) => {
    return of(`retrieved new data with param ${param}`).pipe(
        delay(1000)
    );
};


from([1, 2, 3, 4]).pipe(
    mergeMap(param => getData(param))
).subscribe(val => console.log(val));