Angular 2 - Show loading-information when (observableData | async) is not yet resolved

前端 未结 6 2087
抹茶落季
抹茶落季 2020-12-29 05:13

just as the title says, I want to embrace the power of rxjs Observables.

What I do now:

// dataview.html
Loading data
相关标签:
6条回答
  • 2020-12-29 05:35

    One way to do that without any member property could be evaluating the async observable results in the template: !(yourAsyncData$ | async) or !(yourAsyncData$ | async)?.length.

    For instance: <p-dataView #dv [value]="bikes$ | async" [loading]="!(bikes$ | async)"> ... </p-dataview>

    0 讨论(0)
  • 2020-12-29 05:37

    This is how I do it. Also i use $ at the and of the variable name to remind me that it is a stream.

    // 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);
            });
    }
    
    0 讨论(0)
  • 2020-12-29 05:38

    I Came up with the following:

    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 });
        })
      );
    }
    

    And then in your component:

    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>
    
    0 讨论(0)
  • 2020-12-29 05:40

    I did it by using the async pipe. But this approach still required you to catch it manually to handle the error. See here for more detail.

    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);
    
    0 讨论(0)
  • 2020-12-29 05:49

    Perhaps this could work for you. This show the data when the observable exists and there is async data. Otherwise shows a loading template.

    <ul *ngIf="data$ && (data$ | async);else loading">
        <li *ngFor="let d of data$ | async">{{ d.value }}</li>
    </ul>
    <ng-template #loading>Loading...</ng-template>
    
    
    0 讨论(0)
  • 2020-12-29 05:50

    This is my current best attempt for displaying search results.

    I thought about extending Observable somehow to include an isLoading property - or returning a tuple but in the end a helper function (in my service) that returns a pair of observables seems to be the cleanest way. Like you I was looking for some 'magic' but I can't see any better way to do it than this.


    So in this example I have a FormGroup (a standard reactive form) which contains search criteria:

    { email: string, name: string } 
    

    I get the search criteria from the form's valueChanges observable when it changes.

    Component Constructor

    Note: The search isn't actually run until the criteria change, which is why this is in the constructor.

    // 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
    

    Search Service

    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$ };
    }
    

    Need to find some way to make this generic, but that's pretty easy. Also note that if you don't care about isLoading you simply do searchCustomers(criteria).data and then you're just getting to the data.

    Edit: needed to add an extra ShareReply to prevent search firing twice.

    Component HTML

    Use both customers.data and customers.isLoading as observables as normal. Remember customers is just an object with two observable properties on it.

    <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>
    

    Also note that you need the async pipe for both observables. I realize that looks a little clumsy for the isLoading, I believe that it is faster to use an observable than a property anyway. There could be a refinement to this, but I'm not yet an expert but would certainly welcome improvements.

    0 讨论(0)
提交回复
热议问题