调度动作 N 次,但只使一个 API 调用生效并组合结果 N 次
Dispatch action N times but only make one API call in effect and combine result N times
我在 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
问题!
如何实现?我缺少哪些运算符组合?
我假设它看起来应该更类似于第一张图:
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))
);
正如您所提到的,这是一个很好的练习,但可能应该以其他方式解决。通过 subjects 命令式地获取值不是一个好的解决方案。
我在 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
问题!
如何实现?我缺少哪些运算符组合?
我假设它看起来应该更类似于第一张图:
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))
);
正如您所提到的,这是一个很好的练习,但可能应该以其他方式解决。通过 subjects 命令式地获取值不是一个好的解决方案。