Angular 2 - 当(observableData | async)尚未解决时显示加载信息
Posted
技术标签:
【中文标题】Angular 2 - 当(observableData | async)尚未解决时显示加载信息【英文标题】:Angular 2 - Show loading-information when (observableData | async) is not yet resolved 【发布时间】:2016-11-10 18:03:51 【问题描述】:正如标题所说,我想拥抱 rxjs Observables 的力量。
我现在做什么:
// dataview.html
<div *ngIf="isLoading">Loading data...div>
<ul *ngIf="!isLoading">
<li *ngFor="let d of data"> d.value </li>
</ul>
// dataview.ts
data: any[] = [];
isLoading: boolean = false;
getData()
this.isLoading = true;
this._api.getData().subscribe(
data =>
this.data = data;
this.isLoading = false;
,
error =>
this.error = error;
this.isLoading = false;
);
我想做什么:
1.在我的模板中使用async
管道
将 data
设为 Observable 数组
仍然为用户显示加载信息
我是干净代码的忠实粉丝,那么如何使用 rxjs 和 Angular 2 很好地做到这一点?
【问题讨论】:
Show loading screen when navigating between routes in Angular 2的可能重复 抱歉,这是个不同的问题。我正在为 rxjs Observables 的一般问题寻找一个干净的解决方案。您在链接的问题中提供的解决方案基本上与我现在正在做的方式相同。具体来说,我想利用异步管道的力量。 @AlexanderCiesielski 你有没有得到更好的解决方案?我需要类似的东西——比如一个发出像 data: null, isLoading: true
这样的元组的可观察对象——但一般情况下——如果有一个标准,还有一个标准
另见medium.com/@a.yurich.zuev/…
【参考方案1】:
使用合并更优雅和反应性的方式。
// dataview.html
<ul *ngIf="data$ | async as data; else loading">
<li *ngFor="let d of data"> d.value </li>
</ul>
<ng-template #loading>
<div >Loading data...</div>
</ng-template>
// dataview.ts
data$: Observable<any[] | null>;
getData(): void
const showLoading$: Observable<any[]> = of([]);
const request$: Observable<any[] | null> = this._api.getData().pipe(
map(data => data), // disable loading on successfull request
catchError(() => of([])) // disable loading on error request
);
// 1. showLoading$ will show loading before the request is made
// 2. request$ then request comes in with delay and tell Us if success = any[] or error = null
// 1. 2.
this.data$ = merge(showLoading$, request$);
【讨论】:
【参考方案2】:也许这对你有用。这显示了当可观察对象存在并且存在异步数据时的数据。否则显示加载模板。
<ul *ngIf="data$ && (data$ | async);else loading">
<li *ngFor="let d of data$ | async"> d.value </li>
</ul>
<ng-template #loading>Loading...</ng-template>
【讨论】:
如果请求完成并且返回的数据是一个空列表,那么用户看到加载微调器可能不太好。 空列表仍然是有效的结果,因此它不会显示微调器..【参考方案3】:我就是这样做的。我还在变量名的 and 处使用$
来提醒我它是一个流。
// dataview.html
<div *ngIf="isLoading$ | async">Loading data...</div>
<ul *ngIf="!(isLoading$ | async)">
<li *ngFor="let d of data"> d.value </li>
</ul>
// dataview.ts
data: any[] = [];
isLoading$: BehaviorSubject<boolean> = new BehaviorSubject(false);
getData()
this.isLoading$.next(true);
this._api.getData().subscribe(
data =>
this.data = data;
,
error =>
this.error = error;
,
complete =>
this.isLoading$.next(false);
);
【讨论】:
我希望只做dataObservable = this._api.getData();
然后异步管道订阅它。但也许我过于简单化了。
在模板中使用this._api.getData()
是不明智的,因为它只会触发一个订阅然后完成。您可以在模板中使用另一个流data$
为什么它是一个 BehaviourSubject我想出了以下内容:
export enum ObsStatus
SUCCESS = 'Success',
ERROR = 'Error',
LOADING = 'Loading',
export interface WrapObsWithStatus<T>
status: ObsStatus;
value: T;
error: Error;
export function wrapObsWithStatus<T>(obs: Observable<T>): Observable<WrapObsWithStatus<T>>
return obs.pipe(
map(x => ( status: ObsStatus.SUCCESS, value: x, error: null )),
startWith( status: ObsStatus.LOADING, value: null, error: null ),
catchError((err: Error) =>
return of( status: ObsStatus.ERROR, value: null, error: err );
)
);
然后在你的组件中:
TS
public ObsStatus: typeof ObsStatus = ObsStatus;
public obs$: Observable<WrapObsWithStatus<YOUR_TYPE_HERE>> = wrapObsWithStatus(this.myService.getObs());
HTML
<div *ngIf="obs$ | async as obs" [ngSwitch]="obs.status">
<div *ngSwitchCase="ObsStatus.SUCCESS">
Success! obs.value
</div>
<div *ngSwitchCase="ObsStatus.ERROR">
Error! obs.error
</div>
<div *ngSwitchCase="ObsStatus.LOADING">
Loading!
</div>
</div>
【讨论】:
很高兴听到这个消息!干杯 嗨!是否可以扩展此示例以供 switchmap 使用?如果发生错误,observable 将关闭并停止接收事件。 我编写的代码中没有任何异步内容,所以我不理解您为什么要使用 switchMap。它只是使用一个映射,它是一些同步代码,因此它与 switchMap、mergeMap、concatMap 或任何其他异步运算符无关 :)【参考方案5】:在没有任何成员属性的情况下执行此操作的一种方法是评估模板中的异步可观察结果:!(yourAsyncData$ | async)
或 !(yourAsyncData$ | async)?.length
。
例如:
<p-dataView #dv [value]="bikes$ | async" [loading]="!(bikes$ | async)">
...
</p-dataview>
【讨论】:
【参考方案6】:我通过使用异步管道来做到这一点。但是这种方法仍然需要您手动捕获它来处理错误。详情请参阅here。
app.component.html
<div class="wrapper">
<div class="form-group" *ngIf="pickupLocations$ | async as pickupLocations; else loading">
<ul class="dropdown-menu" *ngIf="pickupLocations.length">
<li *ngFor="let location of pickupLocations">
<strong>location.Key</strong>
</li>
</ul>
<span *ngIf="!pickupLocations.length">There are no locations to display</span>
</div>
<ng-template #loading>
<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>
<span class="sr-only">Loading...</span>
</ng-template>
</div>
app.component.ts
this.pickupLocations$ = this.apiService.getPickupLocations(storeId);
【讨论】:
【参考方案7】:这是我目前显示搜索结果的最佳尝试。
我考虑过以某种方式扩展 Observable 以包含 isLoading 属性 - 或返回一个元组,但最终返回一对可观察对象的辅助函数(在我的服务中)似乎是最干净的方式。像你一样,我一直在寻找一些“魔法”,但我找不到比这更好的方法了。
所以在这个例子中,我有一个 FormGroup
(一种标准的反应形式),其中包含搜索条件:
email: string, name: string
我从表单的valueChanges
observable 中获取搜索条件。
组件构造函数
注意:直到条件更改后,搜索才会真正运行,这就是为什么 this 在构造函数中。
// get debounced data from search UI
var customerSearchCriteria = this.searchForm.valueChanges.debounceTime(1000);
// create a pair of observables using a service (data + loading state)
this.customers = this.customersService.searchCustomers(customerSearchCriteria);
// this.customers.data => an observable containing the search results array
// this.customers.isLoading => an observable for whether the search is running or not
搜索服务
public searchCustomers(searchCriteria: Observable<CustomersSearch>):
data: Observable<CustomerSearchResult[]>,
isLoading: Observable<boolean>
// Observable to track loading state
var isLoading$ = new BehaviorSubject(false);
// Every time the search criteria changes run the search
var results$ = searchCriteria
.distinctUntilChanged()
.switchMap(criteria =>
// update isLoading = true
isLoading$.next(true);
// run search
var search$ = this.client.search(new CustomersSearch(criteria)).shareReplay();
// when search complete set isLoading = false
search$.subscribe( complete: () => isLoading$.next(false) );
return search$;
)
.shareReplay();
return data: results$, isLoading: isLoading$ ;
需要找到一些方法来使其通用,但这很容易。另请注意,如果您不关心 isLoading,您只需执行 searchCustomers(criteria).data
,然后您就可以获取数据。
编辑:需要添加额外的 ShareReply 以防止搜索触发两次。
组件 HTML
像往常一样使用customers.data
和customers.isLoading
作为可观察对象。记住customers
只是一个带有两个可观察属性的对象。
<div *ngIf="customers.isLoading | async">Loading data...</div>
<ul *ngIf="!(customers.isLoading | async)">
<li *ngFor="let d of customers.data | async"> d.email </li>
</ul>
另外请注意,两个 observable 都需要 async
管道。我意识到 isLoading 看起来有点笨拙,我相信无论如何使用 observable 比使用属性更快。可以对此进行改进,但我还不是专家,但肯定会欢迎改进。
【讨论】:
如果你使用<div *ngIf="customters | async as _customers">...</div>
,那么你不必再次订阅observable,并且可以使用存储在_customers
变量中的解析值:_customers.isLoading
和_customers.data
。
以上是关于Angular 2 - 当(observableData | async)尚未解决时显示加载信息的主要内容,如果未能解决你的问题,请参考以下文章
Angular 2 observables:防止组件的变化冒泡
在 Angular 2 中对 observable 进行单元测试