Injectable OnDestroy

In Angular, when we subscribe to observables — especially inside components — it's essential to unsubscribe during the destroy phase to avoid memory leaks. Several approaches exist for handling this, but Angular 16 brings a new option that simplifies the process significantly. In many scenarios, this new approach is likely the simplest, as it handles a lot of the underlying mechanics automatically.

Older techniques for cleaning up subscriptions often introduced considerable boilerplate, making class code harder to read. Take, for instance, a subscription that needs careful handling:

export class Component implements OnInit {
  data;

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

One common pattern involves the takeUntil operator paired with a dedicated 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();
  }
}

There are various other strategies to manage open subscriptions, but whenever you call subscribe within a component class, you typically end up with a fair amount of repetitive code.

Fortunately, Angular 16 introduces a new operator that changes this: takeUntilDestroy. This pipe-able operator behaves much like the earlier example using takeUntil(this.destroyed), but it demands almost no additional setup code from you.

Injectable OnDestroy

Angular 16 makes the OnDestroy lifecycle hook injectable, offering more flexibility than before.

destroyRef = inject(DestroyRef);

This change means we can inject the destroy hook directly into our components rather than defining it as a method. Consequently, our previous takeUntil example can be simplified to 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)
  }
}

Essentially, there's no longer a need to explicitly implement the ngOnDestroy method in your component. The additional logic can be encapsulated within a pipe-able operator, which is precisely what the new feature accomplishes.

takeUntilDestroy

With the takeUntilDestroy operator, cleanup becomes extremely straightforward. Simply add it to your pipe without any arguments, and it will automatically identify the appropriate OnDestroy hook for the current context, leveraging the 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's really all there is to it!

Passing OnDestroy reference

There are situations where you might want to tie a subscription's lifecycle to a different component's destruction. For instance, suppose a parent component holds a subscription that should stay active only while a child component is visible. In such a case, you can inject DestroyRef into the child component:

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

The new takeUntilDestroy operator can then be employed in the parent component to terminate the subscription by passing the child's DestroyRef reference. Below is an example of how the parent component could be implemented:

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

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

The output will continue to log the count as long as the Child component is alive. Once the child is destroyed, the parent's subscription will be terminated automatically.

Enjoy exploring the new capabilities of Angular 16! ?