Angular 2 - Показать информацию о загрузке, когда (observableData | async) еще не разрешено
как гласит название, я хочу принять силу 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
pipe в моем шаблоне
-
Сделайте data
массив Observable
-
Показывать информацию о загрузке для пользователя
Я большой поклонник чистого кода, так как это можно сделать с помощью rxjs и Angular 2?
Ответы
Ответ 1
Вот как я это делаю. Также я использую $
в и имени переменной, чтобы напомнить мне, что это поток.
// 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);
});
}
Ответ 2
Я сделал это, используя асинхронный канал. Но этот подход по-прежнему требовал, чтобы вы вручную поймали его, чтобы справиться с ошибкой. Подробнее см. здесь.
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);
Ответ 3
Я придумал следующее:
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>
Ответ 4
Это моя лучшая попытка показать результаты поиска.
Я думал о том, чтобы расширить Observable, чтобы включить свойство isLoading, или вернуть кортеж, но в конце концов вспомогательная функция (в моей службе), которая возвращает пару наблюдаемых, кажется, является самым чистым способом. Как и вы, я искал "волшебство", но я не вижу лучшего способа сделать это, чем это.
Итак, в этом примере у меня есть FormGroup
(стандартная реактивная форма), которая содержит критерии поиска:
{ email: string, name: string }
Я получаю критерии поиска из формы valueChanges
, наблюдаемой при ее изменении.
Конструктор компонентов
Примечание. Поиск фактически не выполняется до тех пор, пока критерии не изменятся, поэтому это находится в конструкторе.
// 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.data
, так и customers.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>
Также обратите внимание, что для обоих наблюдаемых вам нужен тэг async
. Я понимаю, что выглядит немного неуклюжим для isLoading, я считаю, что в любом случае быстрее использовать видимое свойство. Это может быть утонченностью, но я еще не эксперт, но, безусловно, приветствую улучшения.
Ответ 5
Одним из способов сделать это без какого-либо свойства элемента может быть оценка наблюдаемых асинхронных результатов в шаблоне !(yourAsyncData$ | async)
или !(yourAsyncData$ | async)?.length
.
Например: <p-dataView #dv [value]="bikes$ | async" [loading]="!(bikes$ | async)">... </p-dataview>
Ответ 6
Возможно, это может сработать для вас. Это показывает данные, когда наблюдаемая существует и есть асинхронные данные. В противном случае показывает шаблон загрузки.
<ul *ngIf="data$ && (data$ | async);else loading">
<li *ngFor="let d of data$ | async">{{ d.value }}</li>
</ul>
<ng-template #loading>Loading...</ng-template>