使用RxJs进行响应式负载状态管理



一个经典的任务,当你有一些输入字段,你必须获取一些值的变化。让我们假设我们使用Angular响应式表单。例子:

orders$ = inputControl.valueChanges.pipe(
switchMap((value) => {
return someService.fetch(value);
})
);

现在我们还应该以某种方式管理加载状态。我通常使用tap:

orders$ = inputControl.valueChanges.pipe(
tap(() => { loading = true }), // or loading$.next(true) if loading is a subject
switchMap((value) => {
return someService.fetch(value);
}),
tap(() => { loading = false }), // or loading$.next(false) if loading is a subject
);

然而,似乎我们可以以某种方式避免在tap中赋值,而使用RxJs。但是我找不到一个方法来处理它。

对我来说,理想的解决方案是

orders$ = <some abstraction here that depends on inputControl.valueChanges and fetching>
loading$ = <some abstraction here that depends on fetching>

看看我的库ez-state。

https://github.com/adriandavidbrand/ngx-ez/tree/master/projects/ez-state

https://adrianbrand.medium.com/angular-state-management-using-services-built-with-ez-state-9b23f16fb5ae

orderCache = new EzCache<Order[]>();
order$ = this.orderCache.value$;
loading$ = this.orderCache.loading$;
inputControl.valueChanges.subscribe(value => {
orderCache.load(someService.fetch(value));
});

一个EzCache是一个美化的行为主题,有方法加载,保存,更新和删除和管理状态观察,如加载$,加载$,保存$,保存$等。

我个人会在服务中拥有缓存,并像我写的那篇文章那样公开服务中的可观察对象。

如果你认为加载状态是一个更大的"订单搜索请求"的一部分,那么就更容易理解如何使用RxJS创建一个可观察对象来发出所需的整体状态。

与其有两个独立的可观察对象,orders$loading$,你可以有一个单独的可观察对象,发出两个数据块(,因为它们总是在同一时间改变)。

我们想要创建一个最初发出:

的observable
{ 
isLoading: true, 
results: undefined
}

然后,在收到结果后,发出:

{ 
isLoading: false, 
results: ** orders from api call ** 
}

我们可以通过使用switchMap,mapstartWith来实现:

orderSearchRequest$ = this.searchTerm$.pipe(
switchMap(term => this.searchService.search(term).pipe(
map(results => ({ isLoading: false, results })),
startWith({ isLoading: true, results: undefined })
)),
);
<form [formGroup]="form">
<input formControlName="searchTerm" placeholder="Order Search">
</form>
<div *ngIf="orderSearchRequest$ | async as request">
<div *ngIf="request.isLoading"> loading... </div>
<ul>
<li *ngFor="let order of request.results">
{{ order.description }}
</li>
</ul>
</div>

这是一个工作的StackBlitz演示。

您可以使用RxJS的scan操作符来跟踪加载状态,而不会引入副作用。


// The interface for our loading state
interface LoadingState<T = unknown> {
loading: boolean;
error?: Error | null;
data?: T;
}
// Using scan to manage our loading state
orders$ = this.inputControl.valueChanges.pipe(
switchMap((value) =>
this.someService.fetch(value).pipe(
// We transform data and mark as not loading
map((data) => ({ data, loading: false })),
// In case of error, we catch it and mark as not loading
catchError((error) => of({ error, loading: false })),
// We emit an initial loading state
startWith({ error: null, loading: true })
)
),
// scan accumulates changes and emits the current state
scan((state: LoadingState<Order>, change: LoadingState<Order>) => ({ 
...state, 
...change,
}))
);

如果你打算在应用程序的多个地方使用这个模式,你可能想要创建你自己的可重用RxJS操作符。

例如:

// switch-map-with-loading.ts
export function switchMapWithLoading<T>(
observableFunction: (value: any) => Observable<T>
): OperatorFunction<any, LoadingState<T>> {
return (source: Observable<any>) =>
source.pipe(
switchMap((value) =>
observableFunction(value).pipe(
map((data) => ({ data, loading: false })),
catchError((error) => of({ error, loading: false })),
startWith({ error: null, loading: true })
)
),
scan((state: LoadingState<T>, change: LoadingState<T>) => ({
...state,
...change,
}))
);
}

然后你可以在你的组件中导入操作符,并像这样使用它:

orders$ = this.inputControl.valueChanges.pipe(
switchMapWithLoading<Order>((value) => this.someService.fetch(value))
);

最新更新