← 解説「DestroyRef と takeUntilDestroyed — 後片付けを宣言的に」に戻る
サンプルコードで身につける: DestroyRef と takeUntilDestroyed — 後片付けを宣言的に
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1Before: ngOnDestroy による手動の後片付け
従来の書き方では、Subscriptionをプロパティに保持し、ngOnDestroyで解除する定型コードが必要でした。この3点セット(保持・実装・解除)を毎回書くのが手間で、忘れるとメモリリークになります。
import { Component, OnDestroy } from '@angular/core';
import { Subscription, interval } from 'rxjs';
@Component({ selector: 'app-clock', template: '<p>{{ seconds }}</p>' })
export class ClockComponent implements OnDestroy {
seconds = 0;
private sub: Subscription; // 1. 保持して
constructor() {
this.sub = interval(1000).subscribe((n) => (this.seconds = n));
}
ngOnDestroy() { // 2. フックを実装して
this.sub.unsubscribe(); // 3. 解除する(忘れるとリーク)
}
}
2After: takeUntilDestroyed で1行にする
同じ処理をtakeUntilDestroyedで書き直すと、Subscriptionの保持もngOnDestroyも消えます。pipeに1行挟むだけで、破棄時の解除が自動になります。
import { Component } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';
@Component({ selector: 'app-clock', template: '<p>{{ seconds }}</p>' })
export class ClockComponent {
seconds = 0;
constructor() {
interval(1000)
.pipe(takeUntilDestroyed()) // ← これだけ
.subscribe((n) => (this.seconds = n));
}
}
3DestroyRef.onDestroy にタイマー解除を登録する
RxJS以外の後片付けにはDestroyRefを使います。setIntervalの解除やライブラリの破棄処理を、開始したその場で登録できるのが利点です。開始と片付けがコード上で隣り合うので、対応関係が一目で分かります。
import { Component, inject, DestroyRef } from '@angular/core';
@Component({ selector: 'app-poller', template: '' })
export class PollerComponent {
private destroyRef = inject(DestroyRef);
constructor() {
// 開始と片付けを隣に書ける
const timerId = setInterval(() => this.poll(), 5000);
this.destroyRef.onDestroy(() => clearInterval(timerId));
// 地図ライブラリなどの破棄も同じ形で登録できる
// const map = createMap();
// this.destroyRef.onDestroy(() => map.dispose());
}
poll() {}
}
4コンストラクタの外では destroyRef を渡す
引数なしのtakeUntilDestroyed()が使えるのは、コンストラクタなどの注入コンテキストの中だけです。ボタンのクリックハンドラなど後から呼ばれる場所では、injectしておいたDestroyRefを明示的に渡します。
import { Component, inject, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-report',
template: '<button (click)="download()">レポート取得</button>',
})
export class ReportComponent {
private http = inject(HttpClient);
private destroyRef = inject(DestroyRef); // 先に受け取っておく
download() {
// ここは注入コンテキストの外なので、引数で渡す
this.http
.get('/api/report')
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((data) => console.log(data));
}
}
5後片付けの道具の使い分け早見表
購読の後片付けには複数の道具があり、優先順位があります。そもそも自分でsubscribeしない書き方が最優先で、takeUntilDestroyedはどうしてもsubscribeが要るときの安全網です。
// ── 購読と後片付けの優先順位 ──
//
// 1. テンプレートで値を表示したいだけ
// → asyncパイプ(購読も解除もAngular任せ)
//
// 2. クラス内でシグナルとして使いたい
// → toSignal()(解除は自動)
//
// 3. どうしても subscribe が必要
// (通知を出す・複数の副作用がある等)
// → .pipe(takeUntilDestroyed()) を必ず挟む
//
// 4. RxJS以外の後片付け(タイマー・ライブラリ破棄)
// → inject(DestroyRef).onDestroy(...)
//
// ngOnDestroy を自分で書くのは最後の手段でよい