假设我们有一个带有按钮的仪表板组件和一些显示异步加载的不同数据源的子组件。单击按钮时,父组件将同时获取每个源数据,加载后将其提供给子组件。加载时,每个子组件都会显示一个覆盖加载器。如果任何源加载中出现错误,则会在出现错误的子组件上显示带有错误消息的弹出窗口,并且加载器也会停止。
我想知道如何使用组合所有子流的单个视图模型流来优雅地做到这一点。我在想这样的事情:
combineLatest({
data1: source1$,
data2: source2$,
...
});
// Each source would have something like this
source1$ = clickEvent$.pipe(
switchMap(() => getData(parameters))
.pipe(
map((data) => ({ data, loading: false })),
catchError((error) => of({ error, loading: false })),
startWith({ error: null, loading: true }),
),
);
我遇到的问题是,如果出现错误,我希望能够保留最后加载的数据,同时显示弹出消息,但我无法从 catchError 运算符访问最后的数据。
您需要的是为每个单独的流累积值并保留最后一个成功的值以及当前值。然后,您可以决定下游要做什么,如果您想根据类型(待处理、成功、错误)使用当前的类型,还是使用最后一个有效的类型。
以下是实现这一目标的方法。
首先,我们定义结果的数据结构:
interface ResultPending {
type: 'pending';
loading: true;
}
interface ResultSuccess<Data> {
type: 'success';
loading: false;
data: Data;
}
interface ResultError {
type: 'error';
loading: false;
error: any;
}
type Result<Data> = ResultPending | ResultSuccess<Data> | ResultError;
然后我们使用之前构建的接口创建一个通用函数来描述结果流:
function keepLastValidResult<Data>(): Observable<{
lastValidResult: ResultSuccess<Data>;
lastResult: Result<Data>;
}> {
return (obs$: Observable<Data>) =>
obs$.pipe(
scan((acc, curr) => {
switch (curr.type) {
case 'success':
return {
lastValidResult: curr,
lastResult: curr,
};
case 'pending':
case 'error':
return {
...acc,
lastResult: curr,
};
}
return acc;
})
);
}
现在,我们可以将此自定义运算符应用于各个流:
combineLatest({
data1: source1$.pipe(keepLastValidResult()),
data2: source2$.pipe(keepLastValidResult()),
});
然后在您的最新组合中,您可以检查当前结果的类型以了解它是否成功,如果不成功,请使用
lastValidResult
继续显示最后一个有效结果,顾名思义,但也会显示基于 lastResult
的错误或加载状态。