首页 > 解决方案 > 根据对象的可观察对象过滤可观察对象数组

问题描述

我有一个Observable<Carrier[]>call carriers$,这很少发出。每个Carrier都有它的BehaviorSubject,无论它是否在购物车中,它都会保留。

这是尽可能简化的

interface Carreir {
  id: number;
  inCart: BehaviorSubject<boolean>;
}

export class AppService {
  carriers$: Observable<Carreir[]>; // Let's ignore creation

  // Take all carriers and filter only those that are inCart
  inCart: Observable<Carreir[]> = this.carriers$.pipe(
    // I tried concatMap, mergeMap, flatMap, exaustMap, but 
    // This, unfortunately, does not use the Observable part of it.
    map(carriers => carriers.filter(carrier => carrier.inCart.value)),
  );
}

编辑1:

BehaviorSubjectin的原因Carrier是为了让carriers$observable 不需要刷新过滤、排序等计算量比较大的任务。由Carrier[]3000 多个元素组成。被过滤掉的元素仍然可以成为购物车的一部分(例如:过滤一个城市,将一些添加到购物车,过滤另一个城市并从另一个城市添加更多)。

编辑 2: https ://stackblitz.com/edit/angular-ivy-h4tk8e?file=src%2Fapp%2Fapp.component.ts

标签: rxjs

解决方案


如果我正确理解您的问题,您想要的是过滤购物车中的所有运营商并通过 Observable 发出它们inCart$。载体在放入购物车时true通过 Subject 发射,从购物车中取出时发射。inCartfalse

您从carriers$它开始发出一组运营商。

现在,一个可能的解决方案可能如下所示:

首先,您转换carriers$为布尔值流,这些值是由inCart所有 Carriers 的属性中保存的所有 BehaviorSubjects 发出的值。这可以这样做

inCartBools$ = this.carriers$.pipe(
    // any time carriers$ emit we create an array of BehaviorSubject<bool>, one per Carrier
    switchMap(carriers => carriers.map(c => c.inCart)),
    // then we flatten the Observables to obtains a stream of booleans
    mergeMap(d => d),
  );

但是一个布尔值流不携带任何关于发出真假的 Carrier 的信息,所以我们需要稍微调整一下代码,以便我们获得一个包含 Carrier 的 id 和值的对象流布尔值(以便我们知道 Carrier 是否已添加或删除)。这可以像这样完成

inCartBoolsAndCarrierIds$ = this.carriers$.pipe(
    switchMap(carriers => carriers.map(c => c.inCart.pipe(
      // rather than just a simple bool we return an object containing also the Carrier id
      map(v => ({id: c.id, v}))
    ))),
    mergeMap(d => d),
  );

任何时候 inCartBoolsAndCarrierIds$ 发出,我们都需要将 Carrier id 添加到购物车或将其删除,具体取决于 bool 的值。我们还需要保留我们存储添加的 id 的数组的内存。这可以使用这样的scan运算符来完成

inCart$ = this.carriers$.pipe(
    switchMap(carriers => carriers.map(c => c.inCart.pipe(map(v => ({id: c.id, v}))))),
    mergeMap(d => d),
    // we accumulate the ids of the Carriers in the cart via the scan operator, using a dictionary to store such ids
    scan((acc, val) => {
      if (val.v) {
        acc[val.id] = {id: val.id}
      } else {
        delete acc[val.id]
      }
      return acc
    }, {} as {[k: number]: {id: number}}),
    // eventually we return only the values of the ids
    map(dict => Object.values(dict))
  );

最终代码可以在这个 stackblitz中检查。

作为解决方案,它看起来有点复杂,但是,如果我正确理解了您的问题,这是一个可行的解决方案。


推荐阅读