[javascript] 각도 및 디 바운스

AngularJS에서는 ng-model 옵션을 사용하여 모델을 디 바운스 할 수 있습니다.

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

Angular에서 모델을 어떻게 디 바운스 할 수 있습니까? 문서에서 디 바운스를 검색하려고했지만 아무것도 찾지 못했습니다.

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를 사용 debounceTime()하면 양식 컨트롤의 valueChanges관찰 가능 항목 에서 RxJS 연산자 를 사용하여 디 바운스 할 수 있습니다 .

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();
  }
} 

Plunker

위의 코드에는 아래 주석에서 @albanx의 요청에 따라 창 크기 조정 이벤트를 조절하는 방법에 대한 예제도 포함되어 있습니다.


위의 코드는 아마도 Angular-way 방식이지만 효율적이지 않습니다. 모든 키 입력 및 크기 조정 이벤트는 디 바운스되고 조절 되더라도 변경 감지가 실행됩니다. 다시 말해, 디 바운싱 및 스로틀 링은 변경 감지 실행 빈도에 영향을 미치지 않습니다 . ( Tobias Bosch 의 GitHub 주석 에서이를 확인했습니다.) 플런저를 실행할 때이 내용을 볼 ngDoCheck()수 있으며 입력 상자에 입력하거나 창 크기를 조정할 때 호출 횟수를 확인할 수 있습니다. 크기 조정 이벤트를 보려면 파란색 “x”버튼을 사용하여 플런저를 별도의 창에서 실행하십시오.

보다 효율적인 기술은 Angular의 “영역”외부의 이벤트에서 RxJS Observables를 직접 만드는 것입니다. 이렇게하면 이벤트가 발생할 때마다 변경 감지가 호출되지 않습니다. 그런 다음 구독 콜백 메소드에서 수동으로 변경 감지를 트리거하십시오. 즉, 변경 감지가 호출되는시기를 제어합니다.

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();
  }
} 

Plunker

나는 그것을 정의 하기 위해 ngAfterViewInit()대신에 사용 합니다 .ngOnInit()inputElRef

detectChanges()이 구성 요소와 해당 하위 요소에서 변경 감지를 실행합니다. 루트 구성 요소에서 변경 감지를 실행하려면 (즉, 전체 변경 감지 확인을 실행) ApplicationRef.tick()대신 사용하십시오. ( ApplicationRef.tick()플 런커의 의견에 전화를 걸었습니다.) 전화 tick()하면 전화 가 걸립니다 ngDoCheck().


답변

을 다루지 않으려면 변경 바인딩과 함께 @angular/formsRxJS Subject를 사용하면 됩니다.

view.component.html

<input [ngModel]='model' (ngModelChange)='changed($event)' />

view.component.ts

import { Subject } from 'rxjs/Subject';
import { Component }   from '@angular/core';
import 'rxjs/add/operator/debounceTime';

export class ViewComponent {
    model: string;
    modelChanged: Subject<string> = new Subject<string>();

    constructor() {
        this.modelChanged
            .debounceTime(300) // wait 300ms after the last event before emitting last event
            .distinctUntilChanged() // only emit if value is different from previous value
            .subscribe(model => this.model = model);
    }

    changed(text: string) {
        this.modelChanged.next(text);
    }
}

변경 감지를 트리거합니다. 변경 감지를 트리거하지 않는 방법은 Mark의 답변을 확인하십시오.


최신 정보

.pipe(debounceTime(300), distinctUntilChanged()) rxjs 6에 필요합니다.

예:

   constructor() {
        this.modelChanged.pipe(
            debounceTime(300), 
            distinctUntilChanged())
            .subscribe(model => this.model = model);
    }


답변

지시어로 구현 될 수 있습니다.

import { Directive, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
import { NgControl } from '@angular/forms';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import { Subscription } from 'rxjs';

@Directive({
  selector: '[ngModel][onDebounce]',
})
export class DebounceDirective implements OnInit, OnDestroy {
  @Output()
  public onDebounce = new EventEmitter<any>();

  @Input('debounce')
  public debounceTime: number = 300;

  private isFirstChange: boolean = true;
  private subscription: Subscription;

  constructor(public model: NgControl) {
  }

  ngOnInit() {
    this.subscription =
      this.model.valueChanges
        .debounceTime(this.debounceTime)
        .distinctUntilChanged()
        .subscribe(modelValue => {
          if (this.isFirstChange) {
            this.isFirstChange = false;
          } else {
            this.onDebounce.emit(modelValue);
          }
        });
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

}

처럼 사용

<input [(ngModel)]="value" (onDebounce)="doSomethingWhenModelIsChanged($event)">

구성 요소 샘플

import { Component } from "@angular/core";

@Component({
  selector: 'app-sample',
  template: `
<input[(ngModel)]="value" (onDebounce)="doSomethingWhenModelIsChanged($event)">
<input[(ngModel)]="value" (onDebounce)="asyncDoSomethingWhenModelIsChanged($event)">
`
})
export class SampleComponent {
  value: string;

  doSomethingWhenModelIsChanged(value: string): void {
    console.log({ value });
  }

  async asyncDoSomethingWhenModelIsChanged(value: string): Promise<void> {
    return new Promise<void>(resolve => {
      setTimeout(() => {
        console.log('async', { value });
        resolve();
      }, 1000);
    });
  }
} 


답변

화제가 된 때문에, 답변의 대부분은 일을하지 않습니다각도 6/7/8/9 및 / 또는 기타 libs와 사용합니다.
RxJS가 포함 된 Angular 6+를위한 짧고 간단한 솔루션입니다.

필요한 것을 먼저 가져 오십시오.

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

초기화 ngOnInit:

export class MyComponent implements OnInit, OnDestroy {
  public notesText: string;
  private notesModelChanged: Subject<string> = new Subject<string>();
  private notesModelChangeSubscription: Subscription

  constructor() { }

  ngOnInit() {
    this.notesModelChangeSubscription = this.notesModelChanged
      .pipe(
        debounceTime(2000),
        distinctUntilChanged()
      )
      .subscribe(newText => {
        this.notesText = newText;
        console.log(newText);
      });
  }

  ngOnDestroy() {
    this.notesModelChangeSubscription.unsubscribe();
  }
}

이 방법을 사용하십시오 :

<input [ngModel]='notesText' (ngModelChange)='notesModelChanged.next($event)' />

추신 : 더 복잡하고 효율적인 솔루션을 위해 여전히 다른 답변을 확인하고 싶을 수도 있습니다.


답변

angular1에서와 같이 직접 액세스 할 수 없지만 NgFormControl 및 RxJS 옵저버 블로 쉽게 재생할 수 있습니다.

<input type="text" [ngFormControl]="term"/>

this.items = this.term.valueChanges
  .debounceTime(400)
  .distinctUntilChanged()
  .switchMap(term => this.wikipediaService.search(term));

이 블로그 게시물은이를 명확하게 설명합니다 :
http://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html

자동 완성을위한 것이지만 모든 시나리오에서 작동합니다.


답변

원하는 것을 수행 하는 RxJS (v.6) Observable만들 수 있습니다 .

view.component.html

<input type="text" (input)="onSearchChange($event.target.value)" />

view.component.ts

import { Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';

export class ViewComponent {
    searchChangeObserver;

  onSearchChange(searchValue: string) {

    if (!this.searchChangeObserver) {
      Observable.create(observer => {
        this.searchChangeObserver = observer;
      }).pipe(debounceTime(300)) // wait 300ms after the last event before emitting last event
        .pipe(distinctUntilChanged()) // only emit if value is different from previous value
        .subscribe(console.log);
    }

    this.searchChangeObserver.next(searchValue);
  }


}


답변

lodash를 사용하는 사람은 모든 기능을 쉽게 디 바운싱 할 수 있습니다.

changed = _.debounce(function() {
    console.log("name changed!");
}, 400);

그런 다음 템플릿에 다음과 같은 것을 던져 넣으십시오.

<(input)="changed($event.target.value)" />