【问题标题】:Angular 2 - Show loading-information when (observableData | async) is not yet resolvedAngular 2 - 当(observableData | async)尚未解决时显示加载信息
【发布时间】: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 管道

  1. data 设为 Observable 数组

  2. 仍然为用户显示加载信息

我是干净代码的忠实粉丝,那么如何使用 rxjs 和 Angular 2 很好地做到这一点?

【问题讨论】:

  • 抱歉,这是个不同的问题。我正在为 rxjs Observables 的一般问题寻找一个干净的解决方案。您在链接的问题中提供的解决方案基本上与我现在正在做的方式相同。具体来说,我想利用异步管道的力量。
  • @AlexanderCiesielski 你有没有得到更好的解决方案?我需要类似的东西——比如一个发出像{ data: null, isLoading: true } 这样的元组的可观察对象——但一般情况下——如果有一个标准,还有一个标准

标签: typescript angular rxjs observable


【解决方案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
        猜你喜欢
        • 1970-01-01
        • 2019-11-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多