【问题标题】:Angular - nested REST API calls only returning inner callAngular - 嵌套的 REST API 调用仅返回内部调用
【发布时间】:2020-09-04 08:21:11
【问题描述】:

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

内部调用后的点击(console.log)显示 ShoppingCart 的内容与预期的一样 - 5 个 ShoppingCartItems 使用 Provider 进行了增强。但是,点击订阅会返回 5 个警报,每个警报都包含我想添加为 ShoppingCartItem 属性的提供者。

看来我使用了错误的 mergeMap/concatMap/switchMap - 或者在一个或两个调用结束时没有执行某种“收集”。

电话:

  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。

【问题讨论】:

  • 您似乎想要进行后续调用,其中一个调用取决于外部调用的结果......为此使用switchMap。你可以在这里阅读更多信息:-- medium.com/@luukgruijs/…
  • 没错

标签: angular rxjs rxjs-observables rxjs-pipeable-operators


【解决方案1】:

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

您可以使用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 }))
  );
}

【讨论】:

  • 这也可以——在我添加这个之后。在调用私有方法之前。在我所处的水平上,这可能更清楚 - 即使我不明白 map() 在 enhanceItem() 中发生了什么
  • ({ ...item, provider }) 创建一个新对象,使用spread syntaxitem 中的属性复制到该对象中,并使用shorthand property namesprovider 对象分配给provider 属性。我使用这种语法是为了让 enhanceShoppingCartenhanceItem 不会改变他们的输入,这通常是一个好习惯。
  • 由于两个答案都有效且我无法拆分功劳,所以我随机选择了一个
【解决方案2】:

如果shoppingCart.shoppingCartItems 数组中有 5 个元素,那么可观察的 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 个并行请求的开销不会很大。

【讨论】:

  • 当然看起来很有趣 - 但我有一个愚蠢的问题:我不太明白 ...shoppingCart 和 ...item 代表什么?
  • @fridoo:是的,我已将地图移至forkJoin
  • @morsor:这是破坏赋值运算符。你可以在这里找到更多信息:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • 关于shoppingCart的map里面好像有问题:一个对象字面量在严格模式下不能有多个同名的属性。
  • @morsor:我错误地访问了密钥的属性。我对答案做了两个更改(shoppingCartItems:provider:)。请检查它现在是否有效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-19
  • 2021-04-19
  • 1970-01-01
  • 1970-01-01
  • 2021-05-03
  • 1970-01-01
  • 2023-01-26
相关资源
最近更新 更多