【问题标题】:Dispatch action N times but only make one API call in effect and combine result N timesDispatch action N 次,但只执行一次 API 调用并合并结果 N 次
【发布时间】:2020-02-09 18:54:21
【问题描述】:

我在 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

这对我来说是棘手的部分。如果LoadData 动作已被调度n 次,我需要触发第二个效果n 次,然后结合其他动作有效负载处理数据因此。我现在得到的是第二种效果的一个触发器。

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

在大理石图中我得到类似的东西

我需要的是类似下面的东西。

我知道还有其他方法可以通过更改应用程序架构中的其他内容来解决整个问题,但这是我目前必须解决的问题,在我看来这是一个有趣的rxjs 问题!

如何做到这一点?我缺少什么运算符组合?

【问题讨论】:

  • 也许这在您的实际服务中最容易解决。只需在其中应用一个缓存主题并将其返回给您。通话只完成一次,您的其他要求都已满足。
  • 你怎么知道n事件已经结束了?可以在应用程序中再次触发吗?否则,当触发新的事件集时,您最终将丢失响应 this.service.loadData() 的任何更改。如果可能,请详细说明
  • @ShashankVivek 好问题!在我的真实代码中,它们将分批分派一次,有时会分派一次,尽管不是很频繁。此外,最多 n = 12.
  • @DanielB:恕我直言,你做得不对。使用RxJs 处理这个问题不是一个好习惯。似乎更像是一种掩饰。据我记得,switchMap 使用内部 Observable 切换值,不确定您期望之前的 http 调用被取消的评论。 1. 确认一下,我认为您的 LOAD_DATA 将进行 12 次 API 调用(即针对每个操作事件)2. 如果您是开放的,我可以尝试在没有 Rxjs 的情况下回答,但使用更好的 effect 和 @987654348 @管理

标签: angular rxjs ngrx ngrx-effects


【解决方案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

正如您所提到的,这是一个很好的练习,但可能应该以另一种方式解决。通过主题命令式地戳值并不是一个好的解决方案。

【讨论】:

  • 这太好了,谢谢@NickL!以及规范测试的绝佳示例!我经常发现自己在思考在什么情况下使用什么 rxjs 运算符,而我完全忘记了scan。我将重构代码以便能够以更好的方式实现它,但我也很欣赏以被动方式执行它的挑战和练习!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-17
  • 2018-03-07
  • 1970-01-01
  • 1970-01-01
  • 2016-07-04
  • 2019-02-03
  • 2017-04-30
相关资源
最近更新 更多