1. ホーム
  2. javascript

[解決済み] Angular 2+とデバウンス

2022-03-07 08:47:26

質問

AngularJSで、ng-modelのオプションを使ってモデルをデバウンスすることができました。

ng-model-options="{ debounce: 1000 }"

Angularでモデルをデバウンスするには?
ドキュメントでdebounceを検索してみましたが、何も見つかりませんでした。

https://angular.io/search/#stq=debounce&stp=1

解決策としては、例えばデバウンス機能を自作することでしょうか。

import {Component, Template, bootstrap} from 'angular2/angular2';

// Annotation section
@Component({
  selector: 'my-app'
})
@Template({
  url: 'app.html'
})
// Component controller
class MyAppComponent {
  constructor() {
    this.firstName = 'Name';
  }
    
  changed($event, el){
    console.log("changes", this.name, el.value);
    this.name = el.value;
  }

  firstNameChanged($event, first){
    if (this.timeoutId) window.clearTimeout(this.timeoutID);
    this.timeoutID = window.setTimeout(() => {
        this.firstName = first.value;
    }, 250)
  }
    
}
bootstrap(MyAppComponent);

そして私のhtml

<input type=text [value]="firstName" #first (keyup)="firstNameChanged($event, first)">

しかし、私はビルトイン機能を探しているのですが、Angularにはあるのでしょうか?

どのように解決するのですか?

RC.5へのアップデート

Angular 2では、RxJSオペレータを使用してデバウンスすることができます。 debounceTime() をフォームコントロールの valueChanges 観測可能です。

import {Component}   from '@angular/core';
import {FormControl} from '@angular/forms';
import {Observable}  from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/throttleTime';
import 'rxjs/add/observable/fromEvent';

@Component({
  selector: 'my-app',
  template: `<input type=text [value]="firstName" [formControl]="firstNameControl">
    <br>{{firstName}}`
})
export class AppComponent {
  firstName        = 'Name';
  firstNameControl = new FormControl();
  formCtrlSub: Subscription;
  resizeSub:   Subscription;
  ngOnInit() {
    // debounce keystroke events
    this.formCtrlSub = this.firstNameControl.valueChanges
      .debounceTime(1000)
      .subscribe(newValue => this.firstName = newValue);
    // throttle resize events
    this.resizeSub = Observable.fromEvent(window, 'resize')
      .throttleTime(200)
      .subscribe(e => {
        console.log('resize event', e);
        this.firstName += '*';  // change something to show it worked
      });
  }
  ngDoCheck() { console.log('change detection'); }
  ngOnDestroy() {
    this.formCtrlSub.unsubscribe();
    this.resizeSub  .unsubscribe();
  }
} 

プランカー

上のコードには、下のコメントで @albanx が質問した、ウィンドウのリサイズイベントをスロットルする方法の例も含まれています。


上記のコードはおそらくAngular流のやり方だと思いますが、効率的ではありません。 すべてのキーストロークとすべてのリサイズイベントは、デバウンスされスロットルされているにもかかわらず、結果的に変更検出が実行されることになります。 言い換えれば デバウンスとスロットリングは、変更検知の実行頻度に影響しません。 . (を発見しました)。 GitHubコメント Tobias Boschによるもので、これを確認しました)。 これはプランカーを実行するとわかります。 ngDoCheck() は、入力ボックスに文字を入力したときやウィンドウのサイズを変更したときに呼び出されます。(青い "x" ボタンを使って、plunker を別のウィンドウで実行し、リサイズイベントを見ることができます。)

より効率的なテクニックは、Angularの"zone"の外側で、イベントからRxJS Observablesを自分で作成することです。 この方法では、イベントが発生するたびに変更検出が呼び出されることはありません。そして、subscribeコールバックメソッドで、手動で変更検知をトリガーします。つまり、変更検知がいつ呼び出されるかをコントロールします。

import {Component, NgZone, ChangeDetectorRef, ApplicationRef, 
        ViewChild, ElementRef} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/throttleTime';
import 'rxjs/add/observable/fromEvent';

@Component({
  selector: 'my-app',
  template: `<input #input type=text [value]="firstName">
    <br>{{firstName}}`
})
export class AppComponent {
  firstName = 'Name';
  keyupSub:  Subscription;
  resizeSub: Subscription;
  @ViewChild('input') inputElRef: ElementRef;
  constructor(private ngzone: NgZone, private cdref: ChangeDetectorRef,
    private appref: ApplicationRef) {}
  ngAfterViewInit() {
    this.ngzone.runOutsideAngular( () => {
      this.keyupSub = Observable.fromEvent(this.inputElRef.nativeElement, 'keyup')
        .debounceTime(1000)
        .subscribe(keyboardEvent => {
          this.firstName = keyboardEvent.target.value;
          this.cdref.detectChanges();
        });
      this.resizeSub = Observable.fromEvent(window, 'resize')
        .throttleTime(200)
        .subscribe(e => {
          console.log('resize event', e);
          this.firstName += '*';  // change something to show it worked
          this.cdref.detectChanges();
        });
    });
  }
  ngDoCheck() { console.log('cd'); }
  ngOnDestroy() {
    this.keyupSub .unsubscribe();
    this.resizeSub.unsubscribe();
  }
} 

プランカー

私が使っているのは ngAfterViewInit() の代わりに ngOnInit() を確保するために inputElRef が定義されています。

detectChanges() は、このコンポーネントとその子コンポーネントに対して変更検知を実行します。もし、ルートコンポーネントから変更検知を行いたい場合 (つまり、完全な変更検知チェックを行いたい場合) は、次のようにしてください。 ApplicationRef.tick() の代わりに (私は呼び出しを ApplicationRef.tick() をプランカー内のコメントで表示しています)。を呼び出すことに注意してください。 tick()ngDoCheck() が呼び出されます。