DestroyRef: Your New Angular Friend

Angular v16 introduces DestroyRef, a new provider that simplifies your code by executing cleanup tasks before a scope is destroyed. This eliminates the need for inheritance and makes your code cleaner and easier to understand.

DestroyRef: Your New Angular Friend

Angular has recently launched Angular v16, which includes several new features, including the highly anticipated DestroyRef provider.

Although features like hydration, required inputs, and new signals are significant, DestroyRef directly impacts many of us.

What Does Angular DestroyRef Do?

It enables the creation of common logic that executes some cleanup logic before a scope gets destroyed.

Where scope can be a component, directive, pipe, embedded view, or an instance of EnvironmentInjector.

Here's how to use it:

@Component({ ... })
export class MyComponent {
  constructor() {
    inject(DestroyRef).onDestroy(() => {
      // do something when the component gets destroyed
    })
  }
}

Well, it doesn't do so much you can say, but his role in an Angular application can be much more important than other features.

How DestroyRef Can Simplify The Code

Let's take a look at how we used to unsubscribe before Angular v16:

@Component({...})
export class MyComponent implements OnDestroy {
 readonly onDestroyed$ = new Subject();
 // Some Observable we have to unsubscribe from when component gets destroyed
 readonly someObservable$ = unknownObservable$.pipe(takeUntil(this.onDestroy$)).subscribe()
 
 ngOnDestroy() {
  this.onDestroy$.next('')
  this.onDestroy$.complete()
 }
}

DestroyRef is here to make this action much more straightforward.

We can create an rxjs onDestroyed operator that relies on it:

export function onDestroyed() {
  const subject = new Subject();

  inject(DestroyRef).onDestroy(() => {
    subject.next('');
    subject.complete();
  });

  return <T>() => takeUntil<T>(subject.asObservable());
}

And change the component logic like this:

@Component({...})
export class MyComponent {
 readonly someObservable$ = Observable<unknown>;
 readonly onDestroyed = onDestroyed()

 constructor() {
  this.someObservable$ =  unknownObservable$.pipe(this.onDestroyed()).subscribe()
 }

}

Looks much more simple, right? And the best part? You don't need to use inheritance!

Is the era of the 'ngOnDestroy' coming to an end?

Of course not, ngOndestroy is there to be, it can be used for many other things but DestroyRef is here to replace one of the most used logic we used to write within the ngOnDestroy lifecycle hook - the unsubscription from an observable!  

Pretty neat, right? But what if I tell you we don't even need to create a custom onDestroyed operator?

The Angular Team took care of us and created takeUntilDestroyed operator.

A Step Further, takeUntilDestroyed Operator

Angular 16 comes with a set of new functions from @angular/core/rxjs-interop which are in the developer preview as part of the v16 release!

One of them is takeUntilDestroyed operator.

takeUntilDestroyed operator is a function similar we have written above - onDestroyed.

So you don't need to write the same function again and again throughout all your Angular applications.

Let's take a look at how to implement takeUntilDestroyed operator:

import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

@Component({...})
export class MyComponent {

  // Inside an Injection Context
  constructor() {
    unknownObservable$.pipe(takeUntilDestroyed()).subscribe();
  }
}

You should know that DestoryRef must be used inside an injection context, and  takeUntilDestroyed operator is not an exception.

In this example, Angular is able to use DestroyRef under the hood, but if we want to use it outside the class' constructor we have to pass it:

import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

@Component({...})
export class MyComponent {
  readonly destroyRef = inject(DestroyRef);

  // Outside an Injection Context
  ngOnInit() {
    unknownObservable$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe();
  }
}

Wrapping Up

In summary, Angular v16's new DestroyRef provider simplifies code by automating cleanup tasks before a scope is destroyed. This means you can create cleaner code without needing to use inheritance.

Additionally, the takeUntilDestroyed operator can streamline your code even further.

Give it a try and see how much easier it is to work with Angular!

P.S. Feeling excited? Check out another cool new feature - Required Inputs!