Larry Scroggins
Larry Scroggins

Reputation: 15

How can I chain multiple dependent subscriptions using rxjs operators and transformations?

I have a function that is supposed to pull data from one api then use the result to get data from several additional apis. The first data pull is necessary for all of them, but some of the subsequent calls can be run in parallel and others need data returned by one or more of the parallel calls before they can run. At the moment, I've got some of this working by doing three subscribes inside of another subscribe (that's four subscribes in one function for those of you that are counting), but this smells wrong to me and everything I've read says it's either an anti-pattern or just plain wrong. I need a way to get all of these things to run in the correct order - where an order is necessary - and return my data.

I've looked into several rxjs options and I think that I need to use some combination of forkJoin (for the items that can run in parallel) and mergeMap (for the items that are dependent on the results of the forkJoin). The current setup is to make a call to my service and subscribe, then within that subscribe forkJoin and call several other functions, subscribe to that and within that do two more calls and subscribes to additional calls.

getThingFromService(id: number): any {
  const drinks = {};
  this.thingService.getThingById(id)
    .subscribe((thing) => {
      this.thing = thing;
      forkJoin([
        this.getTea(thing.tea.id),
        this.getCoffee(thing.coffee.id),
        this.getSoda(thing.soda.id)
      ])
      .subscribe((data) => {
        drinks.tea = data[0];
        drinks.coffee = data[1];
        drinks.soda = data[2];
        this.getCoffeeTypeById(drinks.coffee.bean.id)
          .subscribe((bean) => {
            drinks.coffeeBean = bean;
          })
        this.getSodaTypeById(drinks.soda.flavors.id)
          .subscribe((flavor) => {
            drinks.sodaFlavor = flavor;
          });
      });
    )}
}

getTea(id: number) {
  return this.thingService.getTea(id);
}

getCoffee(id: number) {
  return this.thingService.getCoffee(id);
}

getSoda(id: number) {
  return this.thingService.getSoda(id);
}

getCoffeeTypeById(id: number) {
  return this.otherService.getCoffee(id);
}

getSodaTypeById(id: number) {
  return this.yetAnotherService.getSoda(id);
}

Everything about this code bothers me. First, it's confusing to read and not very clear. Next, this is a drastically simplified version. The actual function is about 90 lines long. Then, it doesn't 100% work. I think getSodaTypeById is resolving after everything else and is therefor not available when I need it, so the result if I log it is 'undefined'. Finally, there has to be some way to do everything I want to do that is clear and with just one subscribe.

There is bad code that works, bad code that doesn't work, and bad code that kind of works. I can't decide which of those is the worst thing.

Edit: removed "record" and replaced with "thing"

Upvotes: 0

Views: 1551

Answers (1)

Jake
Jake

Reputation: 181

Some things to consider. Try using pipe operator. If you need to create side effects use tap. For the nested subscriptions try a switchMap. Also you may not need the extra methods that just return a service.

Here is a quick possible solution. All the Best.

Link to example https://stackblitz.com/edit/angular-rxjs-nested-subscriptions?file=src/app/app.component.ts

    this.thingService.getThingById(id).pipe(
      tap((thing) => {
        this.thing = thing;
      }),
      switchMap(_ => forkJoin([
        // NOTE: not sure where record comes from
        // if it is related to thing then it can be get passed down
        this.thingService.getTea(record.tea.id),
        this.thingService.getCoffee(record.coffee.id),
        this.thingService.getSoda(record.soda.id)
      ])),
      switchMap(([tea, coffee, soda]) => forkJoin([
        this.getCoffeeTypeById(coffee.bean.id),
        this.getSodaTypeById(soda.flavors.id)
      ]))).subscribe((data) => {
        drinks.coffeeBean = data[0];
        drinks.sodaFlavor = data[1];
      }
    );

Upvotes: 2

Related Questions