Angular e devolução

160

No AngularJS, posso rebater um modelo usando as opções do modelo ng.

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

Como posso rebater um modelo em Angular? Eu tentei procurar por rejeição nos documentos, mas não consegui encontrar nada.

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

Uma solução seria escrever minha própria função de debounce, por exemplo:

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

E meu html

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

Mas estou procurando uma função de construção, existe uma no Angular?

koningdavid
fonte
3
Isso pode ser relevante github.com/angular/angular/issues/1773 , ainda não aparentemente implícito .
Eric Martinez
Verifique esta publicação para Angular 7 com RxJS v6 freakyjolly.com/…
Code Spy

Respostas:

202

Atualizado para RC.5

Com o Angular 2, podemos rebater usando o operador RxJS debounceTime()em um controle de formulário valueChangesobservável:

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

O código acima também inclui um exemplo de como acelerar eventos de redimensionamento de janelas, conforme solicitado por @albanx em um comentário abaixo.


Embora o código acima seja provavelmente a maneira angular de fazê-lo, ele não é eficiente. Cada pressionamento de tecla e cada evento de redimensionamento, mesmo que sejam debitados e regulados, resultam na execução da detecção de alterações. Em outras palavras, a rejeição e a limitação não afetam a frequência com que a detecção de alterações é executada . (Encontrei um comentário do Tobias Bosch no GitHub que confirma isso.) Você pode ver isso ao executar o desentupidor e quantas vezes ngDoCheck()está sendo chamado quando você digita na caixa de entrada ou redimensiona a janela. (Use o botão azul "x" para executar o plunker em uma janela separada para ver os eventos de redimensionamento.)

Uma técnica mais eficiente é criar o RxJS Observables a partir dos eventos, fora da "zona" do Angular. Dessa forma, a detecção de alterações não é chamada sempre que um evento é disparado. Em seus métodos de retorno de chamada de inscrição, acione manualmente a detecção de alterações - ou seja, você controla quando a detecção de alterações é chamada:

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

Eu uso em ngAfterViewInit()vez de ngOnInit()garantir que inputElRefestá definido.

detectChanges()executará a detecção de alterações neste componente e em seus filhos. Se você preferir executar a detecção de alterações no componente raiz (por exemplo, execute uma verificação completa da detecção de alterações), use-o ApplicationRef.tick(). (Chamo ApplicationRef.tick()nos comentários no desentupidor.) Observe que chamar tick()fará com ngDoCheck()que seja chamado.

Mark Rajcok
fonte
2
@ Mark Rajcok Acho que, em vez de [value], você deve usar [ngModel], porque [value] não atualiza o valor de entrada.
Milad
1
existe algum método genérico de debounce (por exemplo, para aplicar no evento de redimensionamento de janela)?
Albanx
1
@MarkRajcok Creio que a questão CD que você descreveu em sua resposta for resolvido por github.com/angular/zone.js/pull/843
Jefftopia
2
Quando precisaríamos cancelar a assinatura para evitar vazamentos de memória?
slanden
1
@slanden Sim, acccording para netbasal.com/when-to-unsubscribe-in-angular-d61c6b21bad3 , devemos desinscrever de .fromEvent()assinaturas
Jon Onstott
153

Se você não deseja lidar @angular/forms, basta usar um RxJS Subjectcom ligações de alteração.

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

Isso dispara a detecção de alterações. Para uma maneira que não aciona a detecção de alterações, confira a resposta de Mark.


Atualizar

.pipe(debounceTime(300), distinctUntilChanged()) é necessário para o rxjs 6.

Exemplo:

   constructor() {
        this.modelChanged.pipe(
            debounceTime(300), 
            distinctUntilChanged())
            .subscribe(model => this.model = model);
    }
0xcaff
fonte
5
Eu prefiro esta solução! Trabalhou com angular 2.0.0, rxjs 5.0.0-beta 12
alsco77
2
Funcionou perfeitamente, simples e claro, sem forma envolvida. Eu estou no Angular 4.1.3, 5.1.1 rxjs
quinta
Eu acho que essa é uma solução superior, pois tem a opção de trabalhar com formulários, se necessário, mas remove essa dependência, tornando a implementação muito mais simples. Obrigado.
Max
2
.pipe(debounceTime(300), distinctUntilChanged())é necessário para o rxjs 6
Icycool 14/11
A solução me salvou. Eu estava usando o keyUpevento on input.nativeElementem a mat-table, que parou de funcionar quando o número de colunas foi alterado
igorepst 27/12/18
35

Poderia ser implementado como diretiva

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

}

use-o como

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

amostra de componente

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);
    });
  }
} 
Oleg Polezky
fonte
1
com mais importações, que funcionaram para mim: import "rxjs / add / operator / debounceTime"; import "rxjs / add / operator / distinctUntilChanged";
Sbl
2
Esta é de longe o torna o mais simples de implementar a aplicação larga
joshcomley
1
isFirstChange é usado não para emitir na inicialização
Oleg Polezky
2
Funciona no Angular 8 e no rxjs 6.5.2 com as seguintes alterações. Se você deseja usar a sintaxe de import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged';import { debounceTime, distinctUntilChanged } from 'rxjs/operators';this.model.valueChanges .debounceTime(this.debounceTime) .distinctUntilChanged()this.model.valueChanges .pipe( debounceTime(this.debounceTime), distinctUntilChanged() )
canal
1
Trabalha no Angular 9 e no rxjs 6.5.4 com as alterações @kumaheiyama declaradas em seu comentário. Apenas não esqueça de exportar a diretiva no módulo em que você a está criando. E não esqueça de incluir o módulo no qual você está criando esta diretiva no módulo em que a está usando.
Filip Savic
29

Como o tópico é antigo, a maioria das respostas não funciona no Angular 6/7/8/9 e / ou usa outras bibliotecas.
Então, aqui está uma solução curta e simples para Angular 6+ com RxJS.

Importe o material necessário primeiro:

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

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

Use desta maneira:

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

PS: Para soluções mais complexas e eficientes, convém verificar outras respostas.

Just Shadow
fonte
1
Por que você não cancela isso na destruição?
Virendra Singh Rathore
Atualizada. Obrigado por perceber!
Apenas Sombra
1
@JustShadow Thank you! Foi realmente útil.
Niral Munjariya
Isso funciona perfeito na primeira tentativa. Mas quando eu excluo o texto pesquisado de alguma forma, a próxima solicitação demora muito para responder.
Sadiksha Gautam 12/11/19
Isso é estranho. Ainda funciona bem do meu lado. Você poderia compartilhar mais informações ou talvez abrir uma nova pergunta para isso?
Just Shadow
28

Não é acessível diretamente como no angular1, mas você pode jogar facilmente com os observáveis ​​NgFormControl e RxJS:

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

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

Esta postagem do blog explica claramente: http://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html

Aqui é para um preenchimento automático, mas funciona em todos os cenários.

bertrandg
fonte
mas há um erro de serviço, isso não está funcionando novamente
Arun Tyagi
Eu não entendo o exemplo. [...] é uma ligação de destino unidirecional. Por que o contêiner pode ser notificado valueChanges? não deveria ter que ser sth. gosta (ngFormControl)="..."?
phil294
20

Você pode criar um RxJS (v.6) Observable que faz o que quiser.

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


}
Matthias
fonte
Graças que ajudaram, no entanto, penso que a importação deve ser de rsjs/Rx, eu tinha erros ao utilizar a importação do jeito que você escreveu ... Então, no meu caso, é agora:import { Observable } from 'rxjs/Rx';
ghiscoding
2
@ghiscoding Depende da versão do rxjs. Na versão 6, é: import { Observable } from 'rxjs';.
Matthias
Obrigado! Como um aparte, você pode apenas usar uma pipechamadapipe(debounceTime(300), distinctUntilChanged())
al.
1
searchChangeObserver é um Assinante, portanto, searchChangeSubscriber será um nome melhor.
Khonsort 13/09/18
12

Para quem usa o lodash, é extremamente fácil renunciar a qualquer função:

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

então apenas jogue algo assim no seu modelo:

<(input)="changed($event.target.value)" />
Brad C
fonte
3
ou apenas (input) = "alterado ($ event.target.value)" #
Jamie Kudla
1
Obrigado por responder com lodash :)
Vamsi
Acredito que isso ainda acionará a detecção de alterações angulares em todas as alterações, independentemente da devolução.
AsGoodAsItGets
5

Solução com assinante de inicialização diretamente na função de evento:

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

class MyAppComponent {
    searchTermChanged: Subject<string> = new Subject<string>();

    constructor() {
    }

    onFind(event: any) {
        if (this.searchTermChanged.observers.length === 0) {
            this.searchTermChanged.pipe(debounceTime(1000), distinctUntilChanged())
                .subscribe(term => {
                    // your code here
                    console.log(term);
                });
        }
        this.searchTermChanged.next(event);
    }
}

E html:

<input type="text" (input)="onFind($event.target.value)">
Serhii Vasko
fonte
Funciona totalmente bem para a caixa de texto angular de 8 dígitos com preenchimento automático. Muito obrigado.
Jasmin Akther Suma
4

Resolvi isso escrevendo um decorador de debounce. O problema descrito pode ser resolvido aplicando o @debounceAccessor ao acessador definido da propriedade.

Também forneci um decorador de debounce adicional para métodos, que podem ser úteis para outras ocasiões.

Isso facilita muito a rejeição de uma propriedade ou método. O parâmetro é o número de milissegundos que o debounce deve durar, 100 ms no exemplo abaixo.

@debounceAccessor(100)
set myProperty(value) {
  this._myProperty = value;
}


@debounceMethod(100)
myMethod (a, b, c) {
  let d = a + b + c;
  return d;
}

E aqui está o código para os decoradores:

function debounceMethod(ms: number, applyAfterDebounceDelay = false) {

  let timeoutId;

  return function (target: Object, propName: string, descriptor: TypedPropertyDescriptor<any>) {
    let originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      if (timeoutId) return;
      timeoutId = window.setTimeout(() => {
        if (applyAfterDebounceDelay) {
          originalMethod.apply(this, args);
        }
        timeoutId = null;
      }, ms);

      if (!applyAfterDebounceDelay) {
        return originalMethod.apply(this, args);
      }
    }
  }
}

function debounceAccessor (ms: number) {

  let timeoutId;

  return function (target: Object, propName: string, descriptor: TypedPropertyDescriptor<any>) {
    let originalSetter = descriptor.set;
    descriptor.set = function (...args: any[]) {
      if (timeoutId) return;
      timeoutId = window.setTimeout(() => {
        timeoutId = null;
      }, ms);
      return originalSetter.apply(this, args);
    }
  }
}

Eu adicionei um parâmetro adicional para o decorador do método, que permite acionar o método APÓS o atraso do rebounce. Fiz isso para que, por exemplo, pudesse ser usado quando associado a eventos de mouseover ou redimensionamento, onde eu queria que a captura ocorresse no final do fluxo de eventos. Nesse caso, no entanto, o método não retornará um valor.

Fredrik_Macrobond
fonte
3

Podemos criar uma diretiva [debounce] que sobrescreve a função viewToModelUpdate padrão do ngModel por uma vazia.

Código da Diretiva

@Directive({ selector: '[debounce]' })
export class MyDebounce implements OnInit {
    @Input() delay: number = 300;

    constructor(private elementRef: ElementRef, private model: NgModel) {
    }

    ngOnInit(): void {
        const eventStream = Observable.fromEvent(this.elementRef.nativeElement, 'keyup')
            .map(() => {
                return this.model.value;
            })
            .debounceTime(this.delay);

        this.model.viewToModelUpdate = () => {};

        eventStream.subscribe(input => {
            this.model.viewModel = input;
            this.model.update.emit(input);
        });
    }
}

Como usá-lo

<div class="ui input">
  <input debounce [delay]=500 [(ngModel)]="myData" type="text">
</div>
BebbaPig
fonte
2

Arquivo HTML:

<input [ngModel]="filterValue"
       (ngModelChange)="filterValue = $event ; search($event)"
        placeholder="Search..."/>

Arquivo TS:

timer = null;
time = 250;
  search(searchStr : string) : void {
    clearTimeout(this.timer);
    this.timer = setTimeout(()=>{
      console.log(searchStr);
    }, time)
  }
Yoav Schniederman
fonte
2

A solução simples seria criar uma diretiva que você possa aplicar a qualquer controle.

import { Directive, ElementRef, Input, Renderer, HostListener, Output, EventEmitter } from '@angular/core';
import { NgControl } from '@angular/forms';

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

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

    private modelValue = null;

    constructor(public model: NgControl, el: ElementRef, renderer: Renderer){
    }

    ngOnInit(){
        this.modelValue = this.model.value;

        if (!this.modelValue){
            var firstChangeSubs = this.model.valueChanges.subscribe(v =>{
                this.modelValue = v;
                firstChangeSubs.unsubscribe()
            });
        }

        this.model.valueChanges
            .debounceTime(this.debounceTime)
            .distinctUntilChanged()
            .subscribe(mv => {
                if (this.modelValue != mv){
                    this.modelValue = mv;
                    this.onDebounce.emit(mv);
                }
            });
    }
}

uso seria

<textarea [ngModel]="somevalue"   
          [debounce]="2000"
          (onDebounce)="somevalue = $event"                               
          rows="3">
</textarea>
Ashg
fonte
Esta classe está longe de ser compilada Angular 7.
Stephane
1

Passei horas nisso, espero que eu possa economizar alguém mais algum tempo. Para mim, a seguinte abordagem para usar debounceem um controle é mais intuitiva e fácil de entender para mim. Ele foi desenvolvido com base na solução angular.io docs para preenchimento automático, mas com a capacidade de interceptar as chamadas sem precisar depender de vincular os dados ao DOM.

Plunker

Um cenário de caso de uso para isso pode estar verificando um nome de usuário após a digitação para ver se alguém já o usou e avisando o usuário.

Nota: não esqueça, (blur)="function(something.value)pode fazer mais sentido para você, dependendo de suas necessidades.

Helzgate
fonte
1

DebounceTime no Angular 7 com RxJS v6

Link de origem

Link de demonstração

insira a descrição da imagem aqui

No modelo HTML

<input type="text" #movieSearchInput class="form-control"
            placeholder="Type any movie name" [(ngModel)]="searchTermModel" />

No componente

    ....
    ....
    export class AppComponent implements OnInit {

    @ViewChild('movieSearchInput') movieSearchInput: ElementRef;
    apiResponse:any;
    isSearching:boolean;

        constructor(
        private httpClient: HttpClient
        ) {
        this.isSearching = false;
        this.apiResponse = [];
        }

    ngOnInit() {
        fromEvent(this.movieSearchInput.nativeElement, 'keyup').pipe(
        // get value
        map((event: any) => {
            return event.target.value;
        })
        // if character length greater then 2
        ,filter(res => res.length > 2)
        // Time in milliseconds between key events
        ,debounceTime(1000)        
        // If previous query is diffent from current   
        ,distinctUntilChanged()
        // subscription for response
        ).subscribe((text: string) => {
            this.isSearching = true;
            this.searchGetCall(text).subscribe((res)=>{
            console.log('res',res);
            this.isSearching = false;
            this.apiResponse = res;
            },(err)=>{
            this.isSearching = false;
            console.log('error',err);
            });
        });
    }

    searchGetCall(term: string) {
        if (term === '') {
        return of([]);
        }
        return this.httpClient.get('http://www.omdbapi.com/?s=' + term + '&apikey=' + APIKEY,{params: PARAMS.set('search', term)});
    }

    }
Code Spy
fonte
1

Você também pode resolver isso usando decoradores. Para uma instância, usando o decorador debounce de utils-decorator lib ( npm install utils-decorators):

import {debounce} from 'utils-decorators';

class MyAppComponent {

  @debounce(500)
  firstNameChanged($event, first) {
   ...
  }
}
vlio20
fonte
0

Esta é a melhor solução que encontrei até agora. Atualiza o ngModelem bluredebounce

import { Directive, Input, Output, EventEmitter,ElementRef } from '@angular/core';
import { NgControl, NgModel } from '@angular/forms';
import 'rxjs/add/operator/debounceTime'; 
import 'rxjs/add/operator/distinctUntilChanged';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/map';

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

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

    private isFirstChange: boolean = true;

    constructor(private elementRef: ElementRef, private model: NgModel) {
    }

    ngOnInit() {
        const eventStream = Observable.fromEvent(this.elementRef.nativeElement, 'keyup')
            .map(() => {
                return this.model.value;
            })
            .debounceTime(this.debounceTime);

        this.model.viewToModelUpdate = () => {};

        eventStream.subscribe(input => {
            this.model.viewModel = input;
            this.model.update.emit(input);
        });
    }
}

conforme emprestado de https://stackoverflow.com/a/47823960/3955513

Depois, em HTML:

<input [(ngModel)]="hero.name" 
        [debounce]="3000" 
        (blur)="hero.name = $event.target.value"
        (ngModelChange)="onChange()"
        placeholder="name">

No blurmodelo é explicitamente atualizado usando javascript simples.

Exemplo aqui: https://stackblitz.com/edit/ng2-debounce-working

Shyamal Parikh
fonte