web-dev-qa-db-fra.com

Angular 2 - Afficher les informations de chargement lorsque (observableData | async) n'est pas encore résolu

comme le titre le dit, je veux embrasser le pouvoir de Rxjs Observables.

Ce que je fais maintenant: 

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

Ce que je veux faire:

1. Utilisez un tube async dans mon modèle

  1. Faites de data un tableau observable

  2. Toujours afficher les informations de chargement pour l'utilisateur

Je suis un grand fan de code propre, alors comment cela peut-il être fait en utilisant rxjs et Angular 2?

10

C'est comme ça que je le fais. Aussi, j'utilise $ au début et à la fin du nom de la variable pour me rappeler que c'est un flux.

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

Je l'ai fait en utilisant le tuyau asynchrone. Mais cette approche nécessitait encore que vous l'attrapiez manuellement pour gérer l'erreur. Voir ici pour plus de détails.

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

Je suis venu avec ce qui suit:

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

Et puis dans votre composant:

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

C’est ma meilleure tentative actuelle d’affichage des résultats de recherche.

J'ai envisagé d'étendre observable d'une manière ou d'une autre pour inclure une propriété isLoading - ou de retourner un tuple mais à la fin une fonction d'assistance (dans mon service) qui renvoie une paire d'observables semble être la méthode la plus propre. Comme vous, je cherchais de la "magie", mais je ne vois pas de meilleur moyen de le faire que cela. 


Donc, dans cet exemple, j'ai une FormGroup (un formulaire réactif standard) qui contient les critères de recherche:

{ email: string, name: string } 

J'obtiens les critères de recherche à partir de la variable valueChanges du formulaire lorsqu'elle change.

Constructeur de composants

Remarque: La recherche n'est en fait exécutée que lorsque les critères ont été modifiés, raison pour laquelle cela se trouve dans le constructeur.

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

Service de recherche

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

Besoin de trouver un moyen de fabriquer ce générique, mais c'est assez facile. Notez également que si vous ne vous souciez pas de isLoading, il vous suffit de faire searchCustomers(criteria).data pour accéder aux données.

Edit: nécessaire d'ajouter un ShareReply supplémentaire pour empêcher le lancement de la recherche à deux reprises.

Composant HTML

Utilisez à la fois customers.data et customers.isLoading comme observables comme d'habitude. Rappelez-vous que customers est simplement un objet avec deux propriétés observables.

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

Notez également que vous avez besoin du tube async pour les deux observables. Je me rends compte que cela semble un peu maladroit pour le chargement, je pense qu’il est plus rapide d’utiliser un observable qu’un bien de toute façon. Cela pourrait être affiné, mais je ne suis pas encore un expert, mais je serais heureux de recevoir des améliorations.

1
Simon_Weaver

Peut-être que cela pourrait fonctionner pour vous. Cela affiche les données lorsque l'observable existe et qu'il existe des données asynchrones. Sinon, affiche un modèle de chargement.

<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

Une façon de le faire sans aucune propriété de membre pourrait être d’évaluer les résultats observables asynchrones dans le modèle: !(yourAsyncData$ | async) ou !(yourAsyncData$ | async)?.length.

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

0
Alex Fuentes