Angular - 嵌套 REST API 调用仅返回内部调用

Angular - nested REST API calls only returning inner call

通过外部 REST 调用获取带有 ShoppingCartItems 的 ShoppingCart,之后 ShoppingCartItems 的 Observable 进行内部调用以使用 Provider 增强 ShoppingCartItems。

在内部调用之后点击(console.log),显示 ShoppingCart 的内容符合预期 - 5 个 ShoppingCartItems 通过 Provider 增强。然而,点击订阅,returns 5 个警报,每个警报包含我想添加为 属性 ShoppingCartItem 的供应商。

我似乎使用了错误的 mergeMap/concatMap/switchMap - 或者在一个或两个调用结束时没有做某种 'collect'。

来电:

  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)))
            )
        ),
      )
  }

订阅:

  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))
      }
    );
  }

实际 REST 调用:

  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');
  }

非常感谢任何指点。 Angular 版本为 8.

如果 shoppingCart.shoppingCartItems 数组中有 5 个元素,那么 observable from(shoppingCart.shoppingCartItems) 将发射 5 次。这就是您在订阅中观察到的内容。

如果您愿意将使用 concatMap 的顺序请求替换为使用 forkJoin 的并行请求,您可以尝试以下方法

getShoppingCart$(userId: number): Observable < ShoppingCart > {
  return this.rest.getShoppingCart$(userId).pipe(
    mergeMap((shoppingCart) => 
      forkJoin(shoppingCart.shoppingCartItems.map(
        item => this.rest.getProviderByWine$(item.wine.id))
      ).pipe(
        map(providers => ({
          ...shoppingCart,
          shoppingCartItems: shoppingCart.shoppingCartItems.map((item, index) => ({
            ...item,
            provider: providers[index]
          }))
        }))
      );
    ),
    tap((kart) => console.log('ShoppingCart: ' + JSON.stringify(kart)))        // <-- we are priting the output from last operator not the `mergeMap`
  );
}

在请求之后,我们使用 map 运算符返回转换后的 shoppingCart。您还可以将 concatMaptoArray() 运算符一起用于顺序请求,但 5 个并行请求不会产生巨大的开销。

将逻辑拆分成多个函数使得推理更容易并减少嵌套量。 创建一个将提供程序添加到项目的函数和一个将增强项目添加到购物车的函数。

您可以使用forkJoin并行执行多个独立的可观察对象。

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 }))
  );
}