分派动作N次,但实际上仅进行一次API调用,并组合N次结果

时间:2020-02-07 13:53:20

标签: angular rxjs ngrx ngrx-effects

我在Angular应用程序中有相同组件的多个实例。组件的每个实例将调度一个动作[Action] LoadData,以从外部API加载数据。动作的有效负载具有有关分派动作的组件的信息,以便能够将其存储在store中正确的对应位置。

无论操作中包括其他任何有效负载,每次获取的数据都是相同的。既然是这种情况,就不需要我进行多个API调用来加载相同的数据,因此我可以使用switchMap取消以前所有未完成的调用。

为此,我有两种效果,一种是加载数据:

@Effect({ dispatch: false })
loadData$ = this.actions$.pipe(
    ofType<LoadData>(LOAD_DATA),
    switchMap((action) => { // This will cancel out any previous service call
        return this.service.loadData().pipe(
            tap(val => {
                console.log(`Loading data...`)
                this.dataSubject$.next(val) // When the data is fetched, emit on the subject
            })
        );
    })
);

和一个处理已调度动作并与数据结合的动作

@Effect()
handleActionAndDataLoaded$ = combineLatest(
    this.actions$.pipe(
        ofType<LoadData>(LOAD_DATA)
    ),
    this.dataSubject$.asObservable()
).pipe(
    tap(_ => console.log(`All done`))
)

,其中dataSubject$Subject

这是给我带来棘手的部分的地方。如果已n次调度了LoadData次操作,然后需要处理数据,我需要n次触发第二个效果并与其他行动有效载荷相结合。我现在得到的是第二种效果的一个触发。

阅读combineLatest并查看大理石图,这当然是预期的行为。

用大理石图表示,我得到类似的东西

enter image description here

我需要的是下面的内容。

enter image description here

我知道还有其他方法可以通过更改应用程序体系结构中的其他内容来解决整个问题,但这是我目前必须使用的方法,在我看来,这很有趣{{1} }问题!

如何实现?我想念什么运算符组合?

1 个答案:

答案 0 :(得分:1)

我假设它的外观应与第一个图更相似:

actions -1-2-3-----------
data    ---------B-------
result  ---------(1B2B3B)

否则,您似乎希望LoadData操作与以前提取的数据配对。

您可以使用合并和扫描来创建状态机:

const createEffectWithScan = (actions$: Observable<string>, subject$: Observable<string>) =>
  merge(
    actions$.pipe(map(x => ({ type: 'action', value: x }))),
    subject$.pipe(map(x => ({ type: 'data', value: x }))),
  ).pipe(
    scan(
      (a, b) => b.type === 'action'
        ? { data: null, toEmit: [], buffer: [...a.buffer, b.value] }
        : { data: b.value, toEmit: a.buffer, buffer: []  }
      , { data: null, buffer: [], toEmit: [] }
    ),
    filter(x => x.data !== null),
    concatMap(x => x.toEmit.map(a => a + x.data))
  );

Stackblitz

正如您所提到的,这是一个不错的练习,但应该以其他方式解决。强制性地通过主题戳值不是一个好的解决方案。