Як DestroyRef полегшив розробку за допомогою Angular 16
З DestroyRef більше не потрібно використовувати хук життєвого циклу ngOnDestroy для скасування підписки.
Ми чудово знаємо про необхідність завершення підписки, коли компонент знищується, інакше ми призведемо до витоку пам’яті, і наша програма/браузер/машина клієнта ставатиме все повільнішою через велику кількість сміття в пам’яті.
До того, як Observable Pattern додали до Angular 16, використовувалось безліч різних технік:
- Використання екземпляра Subscription — необхідно було оголосити новий екземпляр Subscription, присвоїти йому підписку і викликати його метод unsubscribe, коли компонент буде знищено. Цей підхід мав свої обмеження, оскільки вам доведеться оголошувати окремі змінні для кожної підписки, як показано в прикладі нижче:
export class DemoComponent implements OnInit, OnDestroy {
subscription: Subscription = new Subscription();
ngOnInit(): void {
this.subscription = of([]).subscribe();
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
- Використання масиву підписок — у цьому сценарії ми можемо використовувати скільки завгодно підписок та оперувати ними за допомогою одної змінної. Але якщо ви думаєте про чистий код і читабельність, це теж не здається ідеальним варіантом, оскільки наш код уже обернуто методом push array. З простим прикладом, наведеним нижче, це не здається проблемою, але просто уявіть кілька складніших операцій над кількома потоками:
export class DemoComponent implements OnInit, OnDestroy {
subscriptions: Subscription[] = [];
ngOnInit(): void {
this.subscriptions.push(of([]).subscribe());
}
ngOnDestroy(): void {
this.subscriptions
.forEach((subscription: Subscription) => subscription.unsubscribe());
}
}
- Використання оператора takeUntil з бібліотеки rxjs у поєднанні з Subject. За допомогою цієї техніки ми все ще прописуємо хук життєвого циклу ngOnDestroy, але цього разу нема потреби в ngOnDestroy відписуватися від кожного потоку окремо.
export class DemoComponent implements OnInit, OnDestroy {
private readonly destroy: Subject = new Subject();
ngOnInit(): void {
of([])
.pipe(takeUntil(this.destroy.asObservable()))
.subscribe();
}
ngOnDestroy(): void {
this.destroy.next();
}
}
Звісно, в такому форматі нам треба буде дублювати код в кожному компоненті, де використовуються підписки, але тут вже є декілька варіантів, як можна тримати таку логіку в одному місці, наприклад використання базового компонента, який буде містити логіку для завершення підписок і наслідувати від нього кожен свій компонент, чи використання @Decorator і каcтомних rxjs операторів, чи за допомогою бібліотеки @ngneat/until-destroy.
Нарешті вийшов Angular 16 із чудовим DestroyRef provider, який можна легко використовувати без залучення хука ngOnDestroy. Вам потрібно токен ін’єкції з DestroyRef і оператор takeUntilDestroyed з пакета @angular/core/rxjs-interop:
import { Component, DestroyRef, inject, OnInit } from '@angular/core';
import { of } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-demo',
templateUrl: './demo.component.html',
styleUrls: ['./demo.component.scss'],
})
export class DemoComponent implements OnInit {
private readonly destroy: DestroyRef = inject(DestroyRef);
ngOnInit(): void {
of([])
.pipe(takeUntilDestroyed(this.destroy))
.subscribe();
}
}
Джерела: angular.io, blog.bitsrc.io