A ShoppingCart with ShoppingCartItems is fetched via an outer REST call, after which an Observable of the ShoppingCartItems makes the inner call to enhance the ShoppingCartItems with a Provider.
A tap(console.log) after the inner call, reveals that the contents of the ShoppingCart are as expected - with the 5 ShoppingCartItems enhanced with a Provider. Tapping the subscription however, returns 5 alerts each containing the Provider I wanted to add as a property of ShoppingCartItem.
It seems I am using the wrong mergeMap/concatMap/switchMap - or not doing a 'collect' of some sort at the end of one or both calls.
The calls:
getShoppingCart$(userId: number): Observable<ShoppingCart> {
return this.rest.getShoppingCart$(userId)
.pipe(
mergeMap(
(shoppingCart) => from(shoppingCart.shoppingCartItems)
.pipe(
concatMap(
item => this.rest.getProviderByWine$(item.wine.id)
.pipe(
map(provider => item.provider = provider),
)
),
// Returns ShoppingCart with Providers added
tap(() => console.log('ShoppingCart: ' + JSON.stringify(shoppingCart)))
)
),
)
}
The subscription:
ngOnInit(): void {
this.shoppingCartService.getShoppingCart$(1037).subscribe(
(shoppingCart: ShoppingCart) => {
this.dataSourceShoppingCart = new NestedMatTableDataSource<ShoppingCartItem>(shoppingCart.shoppingCartItems);
// Runs 5 times - each time displaying a Provider, not the ShoppingCart
alert(JSON.stringify(shoppingCart))
}
);
}
The actual REST calls:
getShoppingCart$(userId: number): Observable<ShoppingCart> {
return this.http.get<ShoppingCart>(this.getBaseUrl() + 'users/' + userId + '/shopping-cart');
}
getProviderByWine$(wineId: number): Observable<any> {
return this.http.get<Provider>(this.getBaseUrl() + 'wine/' + wineId + '/provider');
}
Any pointers are greatly appreciated. Angular version is 8.
Splitting the logic into multiple functions makes the reasoning easier and reduces the amount of nesting. Create a function that adds the provider to an item and a function that adds the enhanced items to a shopping cart.
You can use forkJoin
to execute multiple independent observables in parallel.
getShoppingCart$(userId: number): Observable<ShoppingCart> {
return this.rest.getShoppingCart$(userId).pipe(
mergeMap(shoppingCart => enhanceShoppingCart(shoppingCart))
);
}
// Add enhanced items to a shopping cart
private enhanceShoppingCart(shoppingCart: ShoppingCart): Observable<ShoppingCart> {
// Map each shopping cart item to an Observable that adds the provider to it.
const enhancedItems = shoppingCart.shoppingCartItems.map(item => enhanceItem(item))
return forkJoin(enhancedItems).pipe(
map(shoppingCartItems => ({ ...shoppingCart, shoppingCartItems }))
);
}
// Add provider to an item
private enhanceItem(item: Item): Observable<Item> {
return this.rest.getProviderByWine$(item.wine.id).pipe(
map(provider => ({ ...item, provider }))
);
}