takeUntilDestroyed Angular New Way to Unsubscribe
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.

Working stream

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.

Error

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.

Working stream

As you can see in browser our stream is being canceled like before but we wrote much less code here.

And actually if you want to improve your Angular knowledge and prepare for the interview I highly recommend you to check my course Angular Interview Questions - Coding Interview 2023.

📚 Source code of what we've done