Andrew
Andrew

Reputation: 462

How to properly chain concatMaps in Angular RxJS

    this.route.params.pipe(
      concatMap((param) => {
        const ein = param['nonprofit-id'];
        return this.nonprofitService.getNonprofit(ein).pipe(
          concatMap((nonprofit) => {
            this.nonprofit = nonprofit;
            const ratingID = nonprofit.currentRating?.ratingID ? nonprofit.currentRating.ratingID : -1;
            return this.nonprofitService.getRatingForNonprofit(ein, ratingID).pipe(
              concatMap((nonprofitRating) => {
                this.nonprofitRating = nonprofitRating;
                const causeID = this.nonprofit?.cause?.causeID ? this.nonprofit?.cause?.causeID : 0;
                return this.nonprofitService.getSimilarNonprofits(causeID);
              })
            );
          })
        );
      })
    ).subscribe((response) => {
      this.similarNonprofits = response;
    });

I would like to know if the above is the correct way to chain concatMaps in Angular RxJS. The other two calls rely on the "nonprofit" in question being retrieved so that its object can be returned. Rating and Similar Nonprofits can be retrieved at the same time so I was thinking there would be some way I could do this without nesting those concatMaps within each other.

Upvotes: 1

Views: 1300

Answers (2)

Barremian
Barremian

Reputation: 31105

Continuing from @martin's answer, I'd suggest to use forkJoin to combine the last two concatMaps to a single request since they do not depend on each other. This would also trigger the requests in parallel and might help in improving performance (though miniscule).

I've also replaced the ternary operator with the nullish coalescing operator ??.

this.route.params.pipe(
  concatMap((param: any) => {
    const ein = param['nonprofit-id'];
    return this.nonprofitService.getNonprofit(ein).pipe(
      map((nonprofit) => ([ein, nonprofit]))
    );
  }),
  concatMap(([ein, nonprofit]) => 
    forkJoin([
      this.nonprofitService.getRatingForNonprofit(ein, (nonprofit.currentRating?.ratingID ?? -1)),
      this.nonprofitService.getSimilarNonprofits(ein, (this.nonprofit?.cause?.causeID ?? 0))
    ]).pipe(
      map(([nonprofitRating, similarNonprofits]) => ([
        nonprofit,
        nonprofitRating,
        similarNonprofits
      ]))
    )
  )
).subscribe({
  next: ([nonprofit, nonprofitRating, similarNonprofits]) => {
    this.nonprofit = nonprofit;
    this.nonprofitRating = nonprofitRating;
    this.similarNonprofits = similarNonprofits;
  },
  error: (error: any) => {
    // handle error
  }
});

You could also define an object with the properties to avoid using destructuring syntax with multiple elements.

Upvotes: 2

martin
martin

Reputation: 96891

If you need to be using all the previous values it usualy ends with nesting more and more chains but in this case it seems already too complicated so you can map() each inner Observable into an array (or object) like the following:

this.route.params.pipe(
  concatMap((param) => {
    const ein = param['nonprofit-id'];
    return this.nonprofitService.getNonprofit(ein).pipe(
      map(nonprofit => [ein, nonprofit]),
    );
  ),
  concatMap(([ein, nonprofit]) => {
    const ratingID = ...;
    this.nonprofitService.getRatingForNonprofit(ein, ratingID).pipe(
      map(nonprofitRating => [nonprofitRating, ein, nonprofit]),
    );
  }),
  concatMap(([nonprofitRating, ein, nonprofit]) => {
    const causeID = ...;
    return this.nonprofitService.getSimilarNonprofits(causeID).pipe(
      map(similar => [similar, nonprofitRating, ein, nonprofit]),
    );
  }
).subscribe(([similar, nonprofitRating, ein, nonprofit]) => { ... });

Obviously, you don't have to unwrap each array passed as a parameter to consecutive concatMaps but I hope you get the point.

Upvotes: 4

Related Questions