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 而不仅仅是一个布尔值? @SamanthaAdrichem 是 BehaviourSubject 因为这个例子是在带有异步管道的模板中使用流,因为使用流来更新你的模板会使视图的渲染更快 请记住,在执行异步时,它将订阅 Observable。所以在上面的代码中会导致对服务器的两个 api 调用,因为有两个异步管道正在使用。为了防止它,使用 async as blog.angular-university.io/angular-reactive-templates【参考方案4】:

我想出了以下内容:

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.datacustomers.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 比使用属性更快。可以对此进行改进,但我还不是专家,但肯定会欢迎改进。

【讨论】:

如果你使用&lt;div *ngIf="customters | async as _customers"&gt;...&lt;/div&gt;,那么你不必再次订阅observable,并且可以使用存储在_customers变量中的解析值:_customers.isLoading_customers.data

以上是关于Angular 2 - 当(observableData | async)尚未解决时显示加载信息的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2 observables:防止组件的变化冒泡

Angular 2 方法没有订阅 observable

Angular 2 observable 我如何提取响应?

在 Angular 2 中对 observable 进行单元测试

Angular 2 - 使用 Observable 时未加载模型类? [复制]

Angular 2 - Observable 中的排序列表