Оператор takeUntilDestroy() в Angular
Еще в Angular 16 появился оператор takeUntilDestroy()
для RxJS подписок. Он существенно облегчает процесс работы с Observable.
При подписке на Observable
разработчик должен иметь в виду необходимость отписок при уничтожении компонента, чтобы предотвратить утечки памяти. Существует несколько методов, и все они требуют написания лишнего кода.
Например, у нас есть подписка на сервис:
export class Component implements OnInit { data; ngOnInit(): void { this.service.getData().subscribe( response => this.data = response. ) } }
И отписка от него может быть реализована через takeUntil()
и subject
:
export class Component implements OnInit, OnDestroy { data; destroyed = new Subject() ngOnInit(): void { this.service.getData() .pipe( takeUntil(this.destroyed), ) .subscribe( response => this.data = response ) } ngOnDestroy(): void { this.destroyed.next(); this.destroyed.complete(); } }
Injectable OnDestroy
В Angular 16 появилась возможность не наследовать компонент от ngOnDestroy() и просто прокинуть DestroyRef
destroyRef = inject(DestroyRef);
В результате чего пример с takeUntil()
может быть изменен следующим образом:
export class Component implements OnInit { destroyRef = inject(DestroyRef); ngOnInit(): void { const destroyed = new Subject(); this.destroyRef.onDestroy(() => { destroyed.next(); destroyed.complete(); }); this.service.getData() .pipe(takeUntil(destroyed)) .subscribe(response => this.data = response) } }
Команда Angular на этом не остановилась, и предложила свою реализацию для использования в пайпах.
takeUntilDestroy
Вопрос отписок можно упростить используя оператор takeUntilDestroy
. Просто добавьте его в pipe
без передачи каких-либо аргументов, и он автоматически выберет правильный OnDestroy
для текущего контекста — используя инъекцию OnDestroy
.
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; export class Component implements OnInit{ data; constructor(private service: DataService) { this.service.getData() .pipe(takeUntilDestroyed()) .subscribe(response => this.data = response) } }
В большинстве случаев это все что нужно сделать.
В случае, если требуется обработать событие уничтожения другого компонента, takeUntilDestroyed()
может принять аргументом DestroyRef
Например: у родительского компонента есть подписка которая должна оставаться активной до тех пор, пока дочерний компонент показан на экране. Для этого можно внедрить DestroyRef
в дочерний компонент:
export class Child { destroyRef = inject(DestroyRef); }
Мы можем использовать новый оператор takeUntilDestroyed
в родительском компоненте, чтобы закрыть подписку, передавая ссылку на DestroyRef
дочернего компонента. Вот пример реализации для родительского компонента:
export class Parent { @ViewChild(Child) child: Child; ngOnInit(): void { interval(1000) .pipe(takeUntilDestroyed(this.child.destroyRef)) .subscribe((count) => console.log(count)); } }
Пока компонент Child
существует, значение числа из интервала будет попадать в консоль. После его уничтожения подписка в родительском компоненте будет остановлена.