takeUntilDestroyed Angular New Way to Unsubscribe
In this post we will look on takeUntilDestroyed
method that we got inside Angular 16.
I'm sure you remember that inside Angular we must always unsubscribe from our subscriptions to avoid memory leaks.
export class PostsComponent implements OnInit {
data$ = interval(1000);
unsubscribe$ = new Subject<void>();
ngOnInit(): void {
this.data$.pipe(takeUntil(this.unsubscribe$)).subscribe((data) => {
console.log('data', data);
});
}
ngOnDestroy(): void {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
}
Here I have a PostsComponent
with an interval of data inside. Inside ngOnInit
we are subscribing to this stream and logging our data.
When we go to the Posts page we can see logs from our stream. But when we just to Home page our stream stops because we unsubscribed from the stream. This is exactly why here we need unsubscribe Subject
and takeUntil
.
In Angular we have different ways to unsubscribe from the subscriptions but takeUntil
is the most flexible and requires the least amount of code.
But starting from Angular 16 it is even better. As this takeUntil
approach was the most popular we got a helper takeUntilDestroyed
from Angular.
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
...
export class PostsComponent implements OnInit {
...
ngOnInit(): void {
this.data$.pipe(takeUntilDestroyed()).subscribe((data) => {
console.log('data', data);
});
}
}
So instead of all code with Subject that we have previously we can just import takeUntilDestroyed
and use it inside the pipe.
We didn't get any errors inside Typescript but in browser you can see this error. It means that we can use takeUntilDestroyed
only in construction phase and not just anywhere in our code.
Construction phase inside component is either in constructor or by defining values in class.
Which means we need to move our takeUntilDestroyed
to another place.
export class PostsComponent implements OnInit {
data$ = interval(1000).pipe(takeUntilDestroyed())
ngOnInit(): void {
this.data$.subscribe((data) => {
console.log('data', data);
});
}
}
Here we attached takeUntilDestroyed
to our interval. It won't throw any errors because it is a construction phase. Our code is super clean now as there are no pipes in ngOnInit
at all.
This is the 99% of the cases how you want to write your code.
Not construction phase
But maybe you have a case when you really need to use takeUntilDestroyed
in other place (like ngOnInit). We can use that but will a bit of code.
export class PostsComponent implements OnInit {
data$ = interval(1000);
destroyRef = inject(DestroyRef);
ngOnInit(): void {
this.data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => {
console.log('data', data);
});
}
}
Here we injected DestroyRef
and provided it to takeUntilDestroyed
method inside ngOnInit
. It won't throw any errors because we provided the destroy reference of our component inside from construction phase.
If you want to use takeUntilDestroyed anywhere you need to provide destroyRef inside.
As you can see in browser our stream is being canceled like before but we wrote much less code here.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done