This is a post covering a new feature released in Angular 16.
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.
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);
}
}
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);
}
}
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!
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!