Управление подписками в Angular

Встроенный асинхронный конвейер в 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

Tags: ,

Добавить комментарий