FrancescoDonzello
Angular Share on Twitter

New in Angular v16: DestroyRef

5 minutes read
#articles#angular

This is a post covering a new feature released in Angular 16.

Destroy Lifecycle Event

We all know how important is the OnDestroy method of a Component.

Every time that we create a new Subscription within a Component, we immediately feel the pressure of handling the unsubscription.

OnDestroy in Angular < 16

This is a typical example of using the OnDestroy interface in Angular < 16.

import { Component, inject, OnDestroy, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UserService } from '../services/user.service';
import { Subscription } from 'rxjs';
 
@Component({
  selector: 'app-user',
  standalone: true,
  imports: [CommonModule],
  template: `
    <p *ngIf="user">
      {{ user }}
    </p>
  `,
  styleUrls: ['./user.component.scss']
})
export class UserComponent implements OnInit, OnDestroy {
  private readonly _user = inject(UserService);
  private s: Subscription | undefined;
 
  user: string | undefined;
 
  ngOnInit(): void {
    this.s = this._user.user$.subscribe(u => this.user = u)
  }
 
  ngOnDestroy(): void {
    this.s?.unsubscribe()
  }
}

Altough this is a very simple example, it’s already a bit verbose. However, using a BaseComponent we can simplify the code a bit.

import { CommonModule } from "@angular/common";
import { Component, OnInit, inject } from "@angular/core";
import { UserService } from "../services/user.service";
 
 
@Component({ template: '' })
export class BaseComponent implements OnDestroy {
    private readonly subject = new Subject<void>();
 
    autoUnsubscribe<T>(): MonoTypeOperatorFunction<T> {
        return takeUntil(this.subject);
    }
 
    ngOnDestroy(): void {
        this.subject.next();
        this.subject.complete();
    }
}
 
@Component({
  selector: 'app-user-v16',
  standalone: true,
  imports: [CommonModule],
  template: `
    <p *ngIf="user">
      {{ user }}
    </p>
  `,
  styleUrls: ['./user-v16.component.scss']
})
export class UserV16Component extends BaseComponent implements OnInit {
  private readonly _user = inject(UserService);
 
  user: string | undefined;
 
  ngOnInit(): void {
    this._user.user$.pipe(
      this.autoUnsubscribe()
    ).subscribe(u => this.user = u);
  }
}

What’s new in Angular 16

In Angular 16, there is a new injectable thing available: DestroyRef.

Let’s first create a file containing a new helper function.

You can clearly see that the logic about creating a Subject, to be used later in the takeUntil operator, is now moved to the destroyer function.

import { DestroyRef, inject } from "@angular/core";
import { Subject } from "rxjs";
 
export function destroyer() {
    const destroyRef = inject(DestroyRef);
    const subject = new Subject<void>();
 
    // let's register a callback that will be 
    // called when the component is destroyed
    destroyRef.onDestroy(() => {
        subject.next();
        subject.complete();
    });
 
    return subject;
}

Now, let’s see how we can use it in our Component.

First, we don’t need to implement the OnDestroy interface anymore and we’re not extending a BaseComponent anymore.

import { CommonModule } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core';
import { takeUntil } from 'rxjs';
import { destroyer } from '../destroyer';
import { UserService } from '../services/user.service';
 
 
@Component({
  selector: 'app-user-v16',
  standalone: true,
  imports: [CommonModule],
  template: `
    <p *ngIf="user">
      {{ user }}
    </p>
  `,
  styleUrls: ['./user-v16.component.scss']
})
export class UserV16Component implements OnInit {
  private readonly _user = inject(UserService);
  private readonly destroyer$ = destroyer();
 
  user: string | undefined;
 
  ngOnInit(): void {
    this._user.user$.pipe(
      takeUntil(this.destroyer$)
    ).subscribe(u => this.user = u);
  }
}
 

A gem from the Angular team

To make our life easier, the Angular team has also released a new takeUntilDestroy operator.

This operator is available in the @angular-extensions/destroyer package.

import { CommonModule } from '@angular/common';
import { Component, inject, OnInit, DestroyRef } from '@angular/core';
import { UserService } from '../services/user.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
 
 
@Component({
  selector: 'app-user-v16',
  standalone: true,
  imports: [CommonModule],
  template: `
    <p *ngIf="user">
      {{ user }}
    </p>
  `,
  styleUrls: ['./user-v16.component.scss']
})
 
export class UserV16Component implements OnInit {
  private readonly _user = inject(UserService);
  private readonly destroyRef = inject(DestroyRef);
 
  user: string | undefined;
 
  ngOnInit(): void {
    this._user.user$.pipe(
      takeUntilDestroyed(this.destroyRef)
    ).subscribe(u => this.user = u);
  }
}

Et voilà! 🎉

One field in our Component.

Less is more!

Considerations

There is a huge work going on in the Angular team to make the framework more developer-friendly and less verbose. This new destroyRef injectable is already in use from the ongoing integration of Signals in Angular.

So stay tuned for more updates!

← Back to Angular Articles

Was it helpful?

Tell your friends or co-workers.