Aritz
Aritz

Reputation: 31649

Inner map not getting called in Rxjs

I've got this code which uses two pipes to load a product instance and its playlist. If there's an error when loading the playlist, the property is set to null:

getProduct(productId: number): Observable<ProductDTO> {
    return this.internalFindOne(productId).pipe(
      flatMap((productDTO: ProductDTO) => {
        return this.productPlaylistService.getProductPlaylist(productId).pipe(
          map((productPlaylistDTO: ProductPlaylistDTO) => {
            productDTO.playlist = productPlaylistDTO;
            return productDTO;
          }),
          catchError(() => {
            // If product playlist doesn't find anything, we still have to return the product dto object
            productDTO.playlist = null;
            return of(productDTO);
          }),
        );
      })
    );
}

It is already working this way. However, I don't think flatMap should fit here. There's no compiler error, but as far as I know flatMap works in a 1:n fashion, whereas map does it in a 1:1 fashion. My code is expecting a productDTO as an input here and returns the same productDTO modified. But when I replace the flatMap call by just map, the nested map function stops getting called (neither the success or the error parts).

ProductDTO and ProductPlaylistDTO are not iterable classes.

What am I misunderstanding here?

Upvotes: 1

Views: 1700

Answers (1)

Valeriy Katkov
Valeriy Katkov

Reputation: 40612

flatMap's 'flat' prefix means that it flattens the observable it returns and as the result you'll get the returned observable data instead of the observable itself. It's like removing of one level of the nested observables. It works pretty similar to the switchMap. You can read about the difference here.

At the same time map just transforms the data, so if you return an observable from the map() you'll get this observable as the result:

internalFindOne(productId).pipe(
  flatMap(() => {
    return getProductPlaylist(productId);
  }),
  tap((additionalData: ProductPlaylistDTO) => { ... })
);

internalFindOne(productId).pipe(
  map(() => {
    return getProductPlaylist(productId);
  }),
  tap((additionalData$: Observable<ProductPlaylistDTO>) => { ... })
);

But in your case, since these two requests don't depend on each other you can execute them simultaneously using combineLatest:

getProduct(productId: number): Observable<ProductDTO> {
  return combineLatest([
    this.internalFindOne(productId),
    this.productPlaylistService.productPlaylistService(productId).pipe(
      catchError(() => {
        return of(null);
      })
    ),
  ]).pipe(
    map(([ product, playlist ]) => {
      product.playlist = playlist;
      return product;
    })
  );
}

StackBlitz

Upvotes: 1

Related Questions