Angular で複数回発生するイベントを抑制する (debounce)

最近は Angular と戯れています。

Qiita の自動保存のように、入力イベントを検知し、最後の入力から指定時間経過した後に何らかの処理を実行する方法についてメモ。

Component クラス

Component クラスにて、入力イベントを扱う Subject を定義します。

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject, Subscription } from 'rxjs';

@Component(/* 省略 */)
export class TestComponent implements OnInit, OnDestroy {

  // イベントをまとめる Subject
  autoSaveEvent: Subject<void> = new Subject<void>();

  // Subscribe 情報
  autoSaveSubscription: Subscription = null;

  ngOnInit() {
    // "debounceTime()" をコールし、イベントを 5000 ミリ秒抑制してから処理する。
    this.autoSaveSubscription =
      this.autoSaveEvent.debounceTime(5000).subscribe(
        () => { /* 何らかの処理 */ }
      );
  }

  ngOnDestroy() {
    // unsubscribe しないと、別の Component に遷移してもイベントが発行してしまうので注意。
    if (this.autoSaveSubscription != null) {
      this.autoSaveSubscription.unsubscribe();
    }
  }

}

Component クラスでのポイントは以下 2 点です。

  1. Subject<T>#debounceTime(milliSeconds: number) を呼び出し、イベントを指定時間抑制する。
  2. 不要になったら (上記例では、Component のインスタンスが廃棄されたタイミング) unsubscribe する。

特に 2 が重要です。Component が廃棄されても発火したイベントや Subscribe は自動で解除されません。

Template

Template では、Input 要素のイベント発生時に Subject<T>#next() をコールし、イベントを発生させます。

<input type="text" name="input1" [(ngModel)]="input1" (keyup)="autoSaveEvent.next()">
<textarea name="input2" [(ngModel)]="input2" (keyup)="autoSaveEvent.next()">

ここでのポイントは 2 点です。

  1. (keyup) で、keyup イベントにバインドする
  2. Component クラスに Subject を用意したため、複数のイベントを 1 つのイベントソースのようにみなして処理できる。