如何在angular中处理延迟订阅



假设我有两个可观察器要被模板消耗,一个是数据,另一个是加载指示器:

public readonly data$ = this.backend.get<Data>(...).pipe(
finalize((
{
this.isLoadingSubject$.next(1); 
return () => this.isLoadingSubject$.next(-1);
})()
);
private readonly isLoadingSubject$ = new Subject<number>();
public readonly isLoading$ = isLoadingSubject$.pipe(
scan((acc, value) => acc + value, 0),
map((i) => i > 0),
);

关于finalize的说明:我使用IIFE在data$的管道被触发时开始加载,并在它完成时结束加载。我使用的是一个递增或递减的数字,而不是布尔值,因为可能有多个同时的请求(isLoading$机制被许多可观察的管道使用(。

在我的模板中,我这样使用它:

<ng-container *ngIf="data$ | async as data">
{{ data }}  
<button [disabled]="isLoading$ | async">some button</button>
</ng-container>

问题是对isLoading$的订阅很晚:此订阅只在data$发出后发生,而第一个.next(+1)由于没有订阅而被忽略。

如何优雅地解决此问题?

我尝试过的解决方法,但我不喜欢:

  • 立即订阅isLoading$以使其变热似乎很浪费,而且在阅读代码时,不清楚为什么要这样做。正因为如此,如果仅从代码中不清楚它的用途,这似乎是一个糟糕的解决方案
  • 重新排列模板,使isLoading$位于第一个<ng-container>中,然后data$位于第二个<ng-container>中,但当加载为false时,我必须处理*ngIf不渲染模板的问题,因此我必须将其封装在对象中,这似乎又是浪费。而且,这会导致每次加载切换时都会重新渲染所有内容,这很愚蠢
  • 查看了publishReplay()运算符,但该运算符已被弃用
  • data$isLoading$封装在同一个<ng-container>内的对象中,但每当加载指示符发生变化时,整个模板都会被重新渲染,这是非常浪费的——我只想禁用一个按钮

我认为处理此问题最干净的方法是使用您的"工作区#4";;将两段数据放在一个对象中(又名视图模型。Sander Elias在本视频中解释了这种模式(:

const INITIAL_STATE = {
data      : undefined,
isLoading : false,
};
@Component()
class MyComponent {
private isLoadingSubject$ = new Subject<number>();
private isLoading$ = isLoadingSubject$.pipe(
scan((acc, value) => acc + value, 0),
map(i => i > 0),
);
public vm$ = combineLatest({
data      : this.getData()  .pipe(startWith(INITIAL_STATE.data)),
isLoading : this.isLoading$ .pipe(startWith(INITIAL_STATE.isLoading))
});
private getData() {
this.isLoadingSubject.next(1);
return this.backend.get<Data>(...).pipe(
finalize(() => this.isLoadingSubject.next(-1))
);
}
}
<ng-container *ngIf="vm$ | async as vm">
{{ vm.data }}  
<button [disabled]="vm.isLoading">some button</button>
</ng-container>

无需担心:

,但每当加载指示器更改时,整个模板都会重新呈现

在前面提到的视频的这一部分提到了这个问题。

当组合视图模型的任何部分发生更改时,确实会在组件上运行更改检测,但只有实际发生更改的元素才会被重新绘制;因此,如果只有isLoading属性发生更改,则UI中只有按钮会更新。与data的绑定不会受到影响,因为它没有更改。

最新更新