0

I am making an http call that returns an observable of a list of products. For each product I need to subscribe to an observable which contains the price (since it is a separate request from another service).

I think this is working, but not sure if this is how it should be done:

return this.httpService.get(url, config).pipe(map(res => res.data.map(product => {
   let price;
   this.productPriceService.getPriceByProductId(product.id).subscribe(value => price = value);
   return {
       id: product.id,
       name: product.name,
       price
   }
})));

I have also tried the following an I am getting a circular JSON reference error:

return this.httpService.get(url, config).pipe(map(res => res.data.map(product => {
   return {
       id: product.id,
       name: product.name,
       price: this.productApiService.getPriceByProductId(product.id).subscribe(value => price = value)
   }
})));
Blake Rivell
  • 13,105
  • 31
  • 115
  • 231
  • Is the price saved in the Angular project or your backend service? If it is in the backend you should definitely program your method there so, that it returns the price already in the product list. – Florian N. May 18 '21 at 16:46
  • This is basically just https://stackoverflow.com/q/14220321/3001761. The value of `price` is not yet set in the first case, and is the _subscription_ (c.f. promise) in the second case. – jonrsharpe May 18 '21 at 16:47
  • 1
    Does this answer your question? [Promise.all behavior with RxJS Observables?](https://stackoverflow.com/questions/35608025/promise-all-behavior-with-rxjs-observables) – jonrsharpe May 18 '21 at 16:47
  • @FlorianN. Price is in an angular service of its own called ProductPriceService. getPriceByProductId makes an http request and returns an observable that needs to be subscribed to. I know it sounds weird, but I am also making an http request within the service that I am currently in (productService) to get the listing of products. – Blake Rivell May 18 '21 at 16:49

1 Answers1

3

You should use a combination of switchMap and forkJoin operators.

The code should look like the following

return this.httpService.get(url, config).pipe(
  switchMap(
    res => forkJoin(
      res.data.map(product => this.productPriceService.getPriceByProductId(product.id).pipe(map(price => ({
        id: product.id,
        name: product.name,
        price
      }))))
    )
  )
);

getPriceByProductId function returns an observable, which is asynchronous. So you can't use map operator there. When you use map operator, it doesn't wait for that observable to finish and immediately returns the new data.

switchMap operator is creating another observable instance so that you can wait for the response of it too.

In one word, map returns a value and switchMap returns an observable.

Julian W.
  • 1,501
  • 7
  • 20
  • I was in the middle of writing something a tad more verbose ^^ could I suggest that you explain why your code works to the OP? People copy/paste switchMaps and forkJoins all the time and many of the teams I work with don't actually understand what they're doing. – Will Alexander May 18 '21 at 17:10
  • @willAlexander I was going to ask him to provide an explanation. I have an idea how they work based on the rxjs docs but curious as to why we are doing a switchMap instead of a concatMap. – Blake Rivell May 18 '21 at 17:25
  • Isn’t switchMap only good for one of the products and will not work for all of them since the subscriptions are cancelled? – Blake Rivell May 18 '21 at 17:41
  • forkJoin is for it. forJoin operator waits for all the subscriptions being finished. – Julian W. May 18 '21 at 17:42
  • 1
    Your `get` Observable emits once and then completes, so the cancellation side of `switchMap` is never an issue here. The `forkJoin` subscribes to every Observable in the Array you pass it (in this example, you map the `response.data` Array to an Array of Observables which each emit the final product), collects their emissions, and emits them as an Array once they have all completed (which they will because they're also HTTP requests). – Will Alexander May 19 '21 at 16:06