To prevent memory leaks from open subscriptions in Angular we should unsubscribe from open subscriptions on component destroy.
I found this type of construct in SO and other places:
export class Component implements OnInit, OnDestroy {
public 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();
}
}
And in Angular 16 there's a shorter way of doing the same thing with takeUntilDestroyed
:
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export class Component implements OnInit{
public data;
constructor(private service: DataService) {
this.service.getData()
.pipe(takeUntilDestroyed())
.subscribe(response => this.data = response);
}
}
However, in our application we do the following using ngneat/until-destroy
:
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
@UntilDestroy()
@Component({})
export class Component implements OnInit {
public data;
ngOnInit(): void {
this.service.getData()
.pipe(
untilDestroyed(this)
)
.subscribe(response => this.data = response);
}
}
I wander if there's still an advantage of using ngneat
in Angular 16 or if it could be safely replaced by the new takeUntilDestroy
operator. Are there any differences?
What did you try and what were you expecting?
I try to replace untilDestroyed(this)
from ngneat
with takeUntilDestroyed
in Angular 16 and I expect no functional changes.
I run a few tests where I would log whether a subscription is open after the component is destroyed. I can confirm that both options (takeUntilDestroyed
and ngneat/until-destroy
) work exactly in the same way in Angular 16.
One thing to keep in mind is that if takeUntilDestroyed
is used outside a constructor it needs to have a destroy reference:
import { Component, DestroyRef, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export class Component implements OnInit{
public data;
private destroyRef = inject(DestroyRef);
constructor(private service: DataService) {}
public ngOnInit(): void {
this.service.getData()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(response => this.data = response);
}
}
I'm not aware of any edge cases where a different behavior would arise. And I think in Angular 16 there's almost no benefit in using ngneat/until-destroy
instead of takeUntilDestroyed
.