Еще в 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 существует, значение числа из интервала будет попадать в консоль. После его уничтожения подписка в родительском компоненте будет остановлена.
Больше материалов в моём блоге (с хорошей подсветкой синтаксиса)