Angular PR

【Angular】さよならngOnDestroy|takeUntilDestroyedで購読解除を自動化

記事内に商品プロモーションを含む場合があります

こんにちは!

Angularでアプリケーションを開発していると、RxJSのObservableを使ってデータストリームを扱う機会が非常に多くあります。そこで気をつけなければならないのが、不要になったObservableの購読をきちんと解除することです。

購読解除を忘れるとメモリリークが起きるって聞いたけど…
ngOnDestroyで毎回unsubscribe()を書くのが面倒だな。
もっと簡単に購読解除を管理できる方法はないかな?

Angularでの購読管理について、このような疑問や悩みをお持ちではないでしょうか。

この記事では、Angular 16で新しく導入されたtakeUntilDestroyedオペレータの使い方を、基本から分かりやすく解説します。この機能を使うことで、コンポーネントが破棄されるときに自動的にObservableから購読解除できるようになり、コードがシンプルで安全になります。

この記事は次のような方におすすめです。

この記事はこんな人におすすめ!
  • AngularでRxJSのObservableを扱っている方
  • 購読解除の管理を簡単に行いたい方
  • メモリリークを防ぎたいと考えている開発者
  • Angular 16以降の新機能を知りたい方
  • コードの品質を向上させたい初学者から中級者の方

この記事を読めば、takeUntilDestroyedオペレータの仕組みが理解でき、従来の方法と比べてどれだけコードがシンプルになるかが実感できます。より安全で保守しやすいAngularアプリケーションを開発できるようになりましょう。

「購読管理で困っている方」「Angularの最新機能をマスターしたい方」は、ぜひ参考にしてください。

それでは、順を追って詳しく見ていきましょう!

なぜObservableの購読解除が必要なのか

まずはじめに、なぜObservableの購読解除が重要なのかを理解しておきましょう。

RxJSのObservableは、データストリームを扱うための非常に強力な仕組みです。Angularでは、HTTP通信やフォームの値の変化、ルーターのイベントなど、さまざまな場面でObservableが使われています。

Observableをsubscribe()メソッドで購読すると、データが流れてくるたびにコールバック関数が実行されます。しかし、コンポーネントが破棄された後も購読が残ったままになると、不要な処理が実行され続けてしまいます。これがいわゆる「メモリリーク」の原因となり、アプリケーションのパフォーマンス低下や予期しない動作を引き起こします。

そのため、コンポーネントが破棄されるタイミングで、きちんと購読を解除することが非常に重要になります。

従来の購読解除の方法

takeUntilDestroyedが登場する前は、購読解除のために主に次のような方法が使われていました。

Subscriptionオブジェクトを保存してunsubscribeする方法

最も基本的な方法として、subscribe()が返すSubscriptionオブジェクトを変数に保存しておき、ngOnDestroyライフサイクルフックでunsubscribe()を呼び出す方法があります。

import { Component, OnDestroy, signal } from '@angular/core';
import { interval, Subscription } from 'rxjs';

@Component({
  selector: 'app-example',
  template: `<p>カウント: {{ count() }}</p>`
})
export class ExampleComponent implements OnDestroy {
  count = signal(0);
  private subscription!: Subscription;

  constructor() {
    // 1秒ごとにカウントアップするObservableを購読
    this.subscription = interval(1000).subscribe(value => {
      this.count.set(value);
    });
  }

  ngOnDestroy() {
    // コンポーネント破棄時に購読解除
    this.subscription.unsubscribe();
  }
}

この方法は確実ですが、購読が増えるたびにSubscriptionを保存する変数が必要になり、コードが煩雑になりやすい欠点があります。

SubjectとtakeUntilを使う方法

もう一つの一般的な方法は、Subjectを使ってtakeUntilオペレータで購読を管理する方法です。

import { Component, OnDestroy, signal } from '@angular/core';
import { interval, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-example',
  template: `<p>カウント: {{ count() }}</p>`
})
export class ExampleComponent implements OnDestroy {
  count = signal(0);
  private destroy$ = new Subject<void>();

  constructor() {
    interval(1000)
      .pipe(takeUntil(this.destroy$))
      .subscribe(value => {
        this.count.set(value);
      });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

この方法は複数の購読を一度に管理できるメリットがありますが、OnDestroyインターフェースの実装や、ngOnDestroyメソッド内での処理が必要になります。

takeUntilDestroyedとは何か

Angular 16で導入されたtakeUntilDestroyedオペレータは、これまでの購読解除の手間を大幅に削減してくれる新しい機能です。

takeUntilDestroyedは、コンポーネントやディレクティブが破棄されるときに、自動的にObservableを完了させるRxJSオペレータです。このオペレータは@angular/core/rxjs-interopパッケージから提供されており、Angular 16以降で使用できます。

takeUntilDestroyedの最大の特徴は、手動で購読を管理する必要がなくなることです。従来のようにOnDestroyインターフェースを実装したり、Subjectを作成したりする必要がありません。

takeUntilDestroyedの基本的な使い方

takeUntilDestroyedの最もシンプルな使い方は、コンポーネントのコンストラクタ内で使用する方法です。

コンストラクタは「インジェクションコンテキスト」と呼ばれる特別な実行環境にあり、この環境内ではtakeUntilDestroyedを引数なしで呼び出すことができます。

import { Component, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';

@Component({
  selector: 'app-example',
  template: `<p>カウント: {{ count() }}</p>`
})
export class ExampleComponent {
  count = signal(0);

  constructor() {
    // takeUntilDestroyedを使用
    interval(1000)
      .pipe(takeUntilDestroyed())
      .subscribe(value => {
        this.count.set(value);
      });
  }
}

このコードでは、interval(1000)で1秒ごとに値を発行するObservableを作成し、pipe()メソッドでtakeUntilDestroyed()を適用しています。コンポーネントが破棄されると、自動的に購読が解除され、カウントが停止します。

従来の方法と比べて、OnDestroyインターフェースの実装も、ngOnDestroyメソッドも不要になり、コードが非常にシンプルで読みやすくなります。

インジェクションコンテキスト外での使い方(DestroyRef)

コンストラクタ以外のメソッド、例えばngOnInitやボタンクリック時のイベントハンドラなどでtakeUntilDestroyedを使いたい場合もあります。

こうしたインジェクションコンテキスト外でtakeUntilDestroyedを使う場合は、DestroyRefを明示的に渡す必要があります。

DestroyRefは、コンポーネントやディレクティブのライフサイクルを表すオブジェクトで、inject関数を使って取得できます。

import { Component, DestroyRef, inject, OnInit, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';

@Component({
  selector: 'app-example',
  template: `
    <p>通常カウント: {{ count() }}</p>
    <p>高速カウント: {{ fastCount() }}</p>
    <button (click)="startFastCounting()">高速カウント開始</button>
  `
})
export class ExampleComponent implements OnInit {
  count = signal(0);
  fastCount = signal(0);
  private destroyRef = inject(DestroyRef);

  ngOnInit() {
    // DestroyRefを渡してtakeUntilDestroyedを使用
    interval(1000)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(value => {
        this.count.set(value);
      });
  }

  startFastCounting() {
    // メソッド内でも同様に使用できる
    interval(500)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(value => {
        this.fastCount.set(value);
      });
  }
}

inject(DestroyRef)でコンポーネントのDestroyRefを取得し、それをtakeUntilDestroyed(this.destroyRef)のように引数として渡します。これにより、どのメソッドからでも安全に購読管理ができるようになります。

takeUntilDestroyedのメリット

takeUntilDestroyedを使うことで、開発者は多くのメリットを得られます。

コード量の削減

従来の方法と比べて、OnDestroyインターフェースやngOnDestroyメソッド、Subjectの作成が不要になります。これにより、コードの行数が減り、見通しが良くなります。

コードの可読性向上

購読の開始と終了の管理が同じ場所に集約されるため、コードの意図が分かりやすくなります。購読解除のロジックが散らばることがありません。

メモリリークの防止

takeUntilDestroyedは自動的にコンポーネント破棄時に購読を解除するため、購読解除を忘れるリスクが大幅に減ります。これにより、メモリリークを効果的に防止できます。

保守性の向上

購読管理のためのボイラープレートコードが減ることで、コードの修正や拡張がしやすくなります。新しい購読を追加する際も、同じパターンを適用するだけで済みます。

従来の方法との比較

ここで、従来の方法とtakeUntilDestroyedを使った方法を比較してみましょう。

項目 Subject + takeUntil takeUntilDestroyed
コード量 多く、ボイラープレートが必要です。 少なく、シンプルに書けます。
OnDestroyの実装 必要で、インターフェースを実装してメソッドを定義します。 不要で、自動的に管理されます。
複数の購読管理 一つのSubjectで複数を管理できます。 各購読にtakeUntilDestroyedを適用します。
学習コスト RxJSのSubjecttakeUntilの理解が必要です。 使い方がシンプルで理解しやすいです。
エラーリスク ngOnDestroyでの処理を忘れるリスクがあります。 自動化されているためリスクが低いです。

基本的には、Angular 16以降ではtakeUntilDestroyedの使用が推奨されます。コードがシンプルになり、エラーのリスクも減るため、開発効率が大きく向上します。

まとめ

Angular 16で導入されたtakeUntilDestroyedオペレータは、Observableの購読管理を劇的にシンプルにしてくれる強力な機能です。

  • takeUntilDestroyedは、コンポーネント破棄時に自動的にObservableから購読解除するオペレータです。
  • @angular/core/rxjs-interopパッケージから提供されており、Angular 16以降で使用できます。
  • コンストラクタ内では引数なしで使用でき、非常にシンプルに購読管理ができます。
  • インジェクションコンテキスト外では、DestroyRefinject関数で取得して引数に渡します。
  • 従来のSubjecttakeUntilを使った方法と比べて、コード量が減り、可読性が向上します。
  • メモリリークのリスクを減らし、より安全なアプリケーション開発が可能になります。

RxJSのObservableは強力な機能ですが、適切な購読管理が欠かせません。takeUntilDestroyedを活用して、クリーンで保守しやすいAngularアプリケーションを作りましょう。

キャリア形成/給与還元
ひとつひとつ真摯に向き合う企業
ONE_WEDGE社員募集

株式会社 ONE WEDGEでは、新たな仲間を募集しています!

私たちと一緒に、革新的で充実したキャリアを築きませんか?
当社は、従業員が仕事と私生活のバランスを大切にできるよう、充実した福利厚生を整えています。

  • 完全週休2日制(土日休み)で、祝日や夏季休暇、年末年始休暇もしっかり保証!
  • 様々な休暇制度(有給、慶弔、産前・産後、育児、バースデー休暇、有給6日取得で特別休暇付与)を完備!
  • 従業員の成長と健康を支援するための表彰制度、資格取得支援、健康促進手当など!
  • 生活を支えるテレワーク手当、記事寄稿手当、結婚祝金・出産祝金など、様々な手当を提供!
  • 自己啓発としての書籍購入制度や、メンバー間のコミュニケーションを深める交流費補助!
  • 成果に応じた決算賞与や、リファラル採用手当、AI手当など、頑張りをしっかり評価!
  • ワークライフバランスを重視し、副業もOK!

株式会社 ONE WEDGEでは、一人ひとりの従業員が自己実現できる環境を大切にしています。
共に成長し、刺激を与え合える仲間をお待ちしております。
あなたの能力と熱意を、ぜひ当社で発揮してください。
ご応募お待ちしております!

ホームページ、採用情報は下記ボタンからご確認ください!

応募、ご質問など、お問い合わせフォーム、またはX (旧Twitter)、InstagramのDMでお気軽にご相談ください♪

参考資料

※本記事の本文案はAIを活用して作成していますが、記載しているコードは筆者が実際に実行・検証し、内容の正確性を確認したうえで公開しています。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です