在渲染模板之前等待两个 observables(包括失败的一个)
Posted
技术标签:
【中文标题】在渲染模板之前等待两个 observables(包括失败的一个)【英文标题】:Wait for two observables (incl. failed one) before rendering template 【发布时间】:2020-08-07 19:50:36 【问题描述】:我有两个 Observable。只有在 BOTH Observables 完成或失败时才开始渲染模板:
Observable 1 完成且 Observable 2 完成或 Observable 1 完成,但 Observable 2 失败 当 Observable 1 失败时 Observable 2 并不重要,因为模板不会被完全渲染(忽略<any>
类型,这里只是为了简化)
组件:
@Component(
selector: 'app-page',
templateUrl: './page.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
)
export class PageComponent implements OnInit
obs1$ = new Subject<any>();
obs2$ = new Subject<any>();
isLoading = true;
isObs1Error: boolean;
isObs2Error: boolean;
ngOnInit()
this.initializeDataRetrieval();
initializeDataRetrieval()
this.obs1$ = this.obs1Method();
this.obs1$.subscribe((response: any) =>
this.isObs1Error = false;
this.obs1 = response;
this.obs2$ = this.obs2Method();
this.obs2$.subscribe((response: any) =>
this.isObs2Error = false;
this.isLoading = false;
this.obs2 = response;
this.cdr.detectChanges();
);
);
private obs1Method(): any
return this.obs1Service
.getStuff()
.pipe(
catchError(() =>
this.isError = true;
this.isLoading = false;
this.cdr.detectChanges();
return EMPTY;
)
);
private obs2Method(): any
return this.obs2Service
.getStuff()
.pipe(
catchError(() =>
this.isObs2Error = true;
this.isLoading = false;
this.cdr.detectChanges();
return EMPTY;
)
);
canDisplayContent(): boolean
return !this.isLoading && !this.isObs1Error;
模板:
<ng-container *ngIf="isLoading">
<app-loading-indicator></app-loading-indicator>
</ng-container>
<ng-container *ngIf="isObs1Error">
<div class="error">
This Obs1 stuff could not be loaded currently
</div>
</ng-container>
<ng-container *ngIf="canDisplayContent()">
<div class="error" *ngIf="isObs2Error">
Technical error
</div>
More content here which is shown when at least Obs1 doesn't had an error
</div>
所以基本上:
我想等待模板渲染,直到两个 Observable 都完成并在此期间显示加载指示器 当 Obs1 出现错误时显示消息 当 Obs2 出现错误时,使用 Obs2 错误消息呈现第三个ng-container
我确信可以通过使用 ... which RxJS 运算符来简化 TS 代码?尽管阅读了RxJS Operators for Dummies: forkJoin, zip, combineLatest, withLatestFrom,但我不确定这些是否适合。据我了解,例如combineLatest
仅在两个流都成功完成时才会成功...
欢迎任何提示,谢谢。
【问题讨论】:
【参考方案1】:我会这样做:
const decorateObservable = (obs$, key) => obs$.pipe(
mapTo(false), // `false` -> no error
catchError(() => of(true)), // `true` -> error found
map(value => ( key, value )) // Identification
)
const base$ = merge(
decorateObservable(obs1$, 'obs1'),
decorateObservable(obs2$, 'obs2'),
).pipe(
// When the source is unsubscribed(`error`/`complete`),
finalize(() => this.isLoading = false),
share(),
)
const obs1Displayed$ = base$.pipe(
filter(o => o.key === 'obs1'),
map(o => o.value),
)
const obs2Displayed$ = base$.pipe(
filter(o => o.key === 'obs2'),
map(o => o.value),
)
这里使用share()
运算符是因为不需要多次订阅源。
在这种情况下,您将订阅 两次(在模板中),因为两个 显示的 observables 都来自同一个基础。 share
对多播数据生产者做了什么。
share
与 pipe(multicast(() => new Subject()), refCount())
相同。 refCount
表示一旦第一个订阅者进来,就会调用生产者。
换句话说,数据消费者决定了生产者何时开始其逻辑。
请注意,我假设 obs1$
和 obs2$
正在异步生成值。
本质上和做的差不多:
const s = new Subject();
// The subscriptions happen inside the template
s.pipe(filter(o => o.key === 'obs1', ...).subscribe(observer1)
s.pipe(filter(o => o.key === 'obs2', ...).subscribe(observer2)
// And later on...
s.next( key: 'obs1', value: false ) // `obs1$` emitted
这是模板:
<ng-container *ngIf="isLoading">
<app-loading-indicator></app-loading-indicator>
</ng-container>
<ng-container *ngIf="obs1Displayed$ | async">
<div class="error">
This Obs1 stuff could not be loaded currently
</div>
</ng-container>
<ng-container *ngIf="obs2Displayed$ | async">
<div class="error">
Technical error
</div>
More content here which is shown when at least Obs1 doesn't had an error
</div>
【讨论】:
感谢这个鼓舞人心的不同方法。【参考方案2】:我会考虑在这种情况下使用forkJoin
。
代码如下所示
forkJoin(this.obs1Method(), this.obs2Method()).subscribe(
(resp1, resp2) =>
this.isLoading = false;
this.obs2 = resp1;
this.obs2 = resp2;
this.cdr.detectChanges()
)
您可能还需要稍微更改obsxMethod
s 添加tap
以在成功检索数据的情况下将错误属性设置为false,并删除在subscribe
中执行的设置,如下所示
private obs1Method(): any
return this.obs1Service
.getStuff()
.pipe(
tap(() => this.isError = false),
catchError(() =>
this.isError = true;
return EMPTY;
)
);
private obs2Method(): any
return this.obs2Service
.getStuff()
.pipe(
tap(() => this.isObs2Error = false),
catchError(() =>
this.isObs2Error = true;
return EMPTY;
)
);
【讨论】:
谢谢,目前正在观察 ;) 你的想法。发现:由于forkJoin
的弃用警告,我需要将两个 Observable 包装在一个数组中。以上是关于在渲染模板之前等待两个 observables(包括失败的一个)的主要内容,如果未能解决你的问题,请参考以下文章
Angular2 Observable - 在继续之前等待多个函数调用
在得到我的 Observable 之前等待一个 Promise