wake-up-neo.com

Angular 2 - Ladeinformationen anzeigen, wenn (observableData | async) noch nicht aufgelöst ist

genau wie der Titel sagt, möchte ich die Kraft von rxjs Observables nutzen.

Was mache ich nun: 

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

Was ich machen will; was ich vorhabe zu tun:

1. Verwende async pipe in meiner Vorlage

  1. Machen Sie data zu einem beobachtbaren Array

  2. Ladeinformationen für den Benutzer werden weiterhin angezeigt

Ich bin ein großer Fan von sauberem Code. Wie kann ich dies mit rxjs und Angular 2 tun?

10

So mache ich es. Außerdem benutze ich $ am und vom Variablennamen, um mich daran zu erinnern, dass es sich um einen Stream handelt.

// 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);
        });
}
11
Kliment

Ich habe es mit der asynchronen Pipe gemacht. Bei diesem Ansatz müssen Sie ihn jedoch manuell abfangen, um den Fehler zu beheben. Siehe hier für weitere Details.

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);
2
trungk18

Ich kam mit folgendem auf:

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

Und dann in deiner Komponente:

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

Dies ist mein derzeit bester Versuch, Suchergebnisse anzuzeigen.

Ich dachte darüber nach, Observable irgendwie zu erweitern, um eine isLoading-Eigenschaft hinzuzufügen - oder einen Tuple zurückzugeben, aber am Ende scheint eine Hilfsfunktion (in meinem Dienst), die ein Paar Observables zurückgibt, der sauberste Weg zu sein. Wie Sie war ich auf der Suche nach etwas "Magie", aber es gibt keinen besseren Weg, als dies zu tun. 


In diesem Beispiel habe ich also eine FormGroup (ein standardisiertes reaktives Formular), die Suchkriterien enthält:

{ email: string, name: string } 

Die Suchkriterien erhalte ich aus der valueChanges des Formulars, die bei Änderungen angezeigt wird.

Komponentenkonstruktor

Anmerkung: Die Suche wird erst ausgeführt, wenn sich die Kriterien ändern. Aus diesem Grund befindet sich diese im Konstruktor.

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

Suchdienst

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

Sie müssen einen Weg finden, um dieses Generikum zu erstellen, aber das ist ziemlich einfach. Beachten Sie auch, dass Sie, wenn Sie sich nicht für isLoading interessieren, einfach searchCustomers(criteria).data ausführen und dann einfach zu den Daten gelangen.

Bearbeiten: Erforderlich, um ein zusätzliches ShareReply hinzuzufügen, um zu verhindern, dass die Suche zweimal ausgelöst wird.

Komponenten-HTML

Verwenden Sie customers.data und customers.isLoading wie gewohnt als Observables. Denken Sie daran, dass customers nur ein Objekt mit zwei beobachtbaren Eigenschaften ist.

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

Beachten Sie auch, dass Sie die async-Pipe für beide Observables benötigen. Mir ist klar, dass das isLoading etwas unbeholfen aussieht. Ich glaube, es ist ohnehin schneller, ein Observable als ein Property zu verwenden. Es könnte eine Verfeinerung geben, aber ich bin noch kein Experte, würde aber sicherlich Verbesserungen begrüßen.

1
Simon_Weaver

Vielleicht könnte das für Sie funktionieren. Dies zeigt die Daten, wenn das Observable existiert und asynchrone Daten vorhanden sind. Ansonsten wird eine Ladevorlage angezeigt.

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

Eine Möglichkeit, dies ohne Mitgliedseigenschaft zu tun, besteht in der Auswertung der asynchronen beobachtbaren Ergebnisse in der Vorlage: !(yourAsyncData$ | async) oder !(yourAsyncData$ | async)?.length.

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

0
Alex Fuentes