When subscribing to observables (especially in our components) we have to unsubscribe on destroy to prevent any memory leaks in our application.

There are several options to accomplish this. Now Angular v.16 offers us another (and better) alternative.

Previous methods of unsubscribing from subscriptions added a lot of boilerplate to our classes, which reduced readability. For example, consider the following subscription that needs to be safely handled:

export class Component implements OnInit {
  data;

  ngOnInit(): void {
    this.service.getData().subscribe(
	    response => this.data = response.
    )
  }
}

We could use takeUntil operator with additional 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();
  }
}

But as long as we are calling the subscribe in the component’s class we will end up with many boilerplate code.

Injectable OnDestroy

Angular 16 introduced a flexible ngOnDestroy, which makes the OnDestroy hook injectable.

destroyRef = inject(DestroyRef);

This allows us to inject it into our components instead of using it as a method. As a result, we can modify our takeUntilexample to something like this:

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)
  }
}

This basically means that we don’t need to implement the ngOnDestroy method in our component. All the “additional” code can be wrapped within a pipe-able operator, which is what happened.

Special case

In some cases, we may want to react to the destroy event of another component. For example, consider a scenario where a parent component has a subscription that needs to remain active as long as the child component is on the screen. In this case, we can inject DestroyRef in the child component:

export class Child {
  destroyRef = inject(DestroyRef);
}

We can use the new takeUntilDestroyed operator in the parent component to close the subscription by passing the reference to the DestroyRef of the child component. Here is an example implementation for the parent component:

export class Parent {
  @ViewChild(Child) child: Child;

  ngOnInit(): void {
     interval(1000)
       .pipe(takeUntilDestroyed(this.child.destroyRef))
       .subscribe((count) => console.log(count));
  }
}

The count will be logged to the console as long as the Child component exists. Upon its destruction, the subscription in the Parent will be stopped.

takeUntilDestroy

A new operator which comes into play in Angular 16 — the takeUntilDestroy. This pipe-able operator functions similarly to the example above with takeUntil(this.destroyed), but with almost zero additional code required!

We Just have to add it to the pipe without passing anything, and it will automatically pick up the right OnDestroy for the current context — using injectable 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)
  }
}

That is all!

References:

https://indepth.dev/posts/1518/takeuntildestroy-in-angular-v16

By Shabazz

Software Engineer, MCSD, Web developer & Angular specialist

Leave a Reply

Your email address will not be published. Required fields are marked *