Встроенный асинхронный конвейер в Angular 2+ — это отличный инструмент для управления наблюдаемыми подписками. С его помощью в большинстве случаев можно избежать ручной подписки на наблюдаемые объекты в классах компонентов.
Допустим, мы хотим получить наблюдаемый объект, который каждую секунду выдает нам текущее время. Без асинхронного конвейера мы могли бы сделать что-то вроде этого:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Subscription } from 'rxjs/Subscription'; import 'rxjs/add/observable/interval'; import 'rxjs/add/operator/map'; @Component({ selector: 'app-root', template: `Time: {{ time | date:'mediumTime' }}` }) export class AppComponent implements OnInit, OnDestroy { time: Date; timeSub: Subscription; ngOnInit() { this.timeSub = Observable .interval(1000) .map(val => new Date()) .subscribe(val => this.time = val); } ngOnDestroy() { this.timeSub.unsubscribe(); } }
В хуке OnInit мы создали наблюдаемый объект, который каждую секунду генерирует значение и сопоставляет его с новой датой. Затем мы подписались на этот наблюдаемый объект и установили выдаваемое значение для переменной класса времени. Мы также позаботились о том, чтобы вовремя отказаться от подписки на наблюдаемый объект, когда компонент будет уничтожен.
В шаблоне мы использовали встроенный канал даты, чтобы преобразовать дату в желаемый формат (минуты и секунды).
Однако у нас получилось довольно много шаблонного кода, а еще мы рискуем создать утечку памяти, если забудем своевременно отказаться от подписки. Этот код можно значительно упростить. Вот те же функции, но реализованные с помощью асинхронного конвейера:
import { Component } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/interval'; import 'rxjs/add/operator/map'; @Component({ selector: 'app-root', template: `Time: {{ time$ | async | date:'mediumTime' }}` }) export class AppComponent { time$ = Observable .interval(1000) .map(val => new Date()); }
Асинхронный конвейер управляет подпиской и разворачиванием данных, а также отменяет подписку при уничтожении компонента.
Мы также можем использовать асинхронный конвейер для развертывания и передачи данных на вход для дочернего компонента:
<app-child [time]="time$ | async"></app-child>
И теперь дочернему компоненту нужно только отобразить данные.
Директива ngFor
Допустим, у нас есть немного более сложная структура данных, доступная как observable, и мы установили искусственную задержку в 1 секунду перед получением ее значения (имитируя сетевой запрос):
import { Component } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/delay'; @Component({ ... }) export class AppComponent { cities$ = Observable .of([ {name: 'Los Angeles', population: '3.9 million', elevation: '233′'}, {name: 'New York', population: '8,4 million', elevation: '33′'}, {name: 'Chicago', population: '2.7 million', elevation: '594′'}, ]) .delay(1000); }
В шаблоне мы можем развернуть и подписаться на данные (когда они поступят) с помощью директивы ngFor:
<ul> <li *ngFor="let city of cities$ | async"> Name: {{ city.name }}, Population: {{ city.population }}, Elevation: {{ city.elevation }}</li> </ul>
Директива ngIf
Вот пример использования структурной директивы ngIf. Наш observable выглядит так:
word$ = Observable.of('Abibliophobia');
А шаблон – так:
<span *ngIf="(word$ | async)?.length > 9; else shortWord"> Long word: {{ word$.value }} </span> <ng-template #shortWord> Short word: {{ word$.value }} </ng-template>
В результате мы получим:
Long word: Abibliophobia
Обратите внимание: мы передаем observable word$ через async, но заключаем его в круглые скобки, чтобы затем можно было проверить длину развернутого значения. Мы также используем оператор ?, чтобы избежать ошибки, когда значение word$ еще не доступно.
Читайте также: Управление подпиской RxJS с помощью takeUntil