Angulaire et anti-rebond


160

Dans AngularJS, je peux anti-rebondir un modèle en utilisant les options ng-model.

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

Comment puis-je supprimer un modèle dans Angular? J'ai essayé de rechercher anti-rebond dans la documentation mais je n'ai rien trouvé.

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

Une solution serait d'écrire ma propre fonction anti-rebond, par exemple:

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

Et mon html

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

Mais je recherche une fonction intégrée, y en a-t-il une dans Angular?


3
Cela pourrait être pertinent github.com/angular/angular/issues/1773 , pas encore implémenté apparemment.
Eric Martinez

Consultez cet article pour Angular 7 avec RxJS v6 freakyjolly.com
Code Spy

Réponses:


202

Mis à jour pour RC.5

Avec Angular 2, nous pouvons anti-rebond en utilisant l'opérateur RxJS debounceTime()sur l' valueChangesobservable d' un contrôle de formulaire :

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

Le code ci-dessus comprend également un exemple de la façon de limiter les événements de redimensionnement de la fenêtre, comme demandé par @albanx dans un commentaire ci-dessous.


Bien que le code ci-dessus soit probablement la manière angulaire de le faire, il n'est pas efficace. Chaque frappe et chaque événement de redimensionnement, même s'ils sont déboncés et limités, entraînent l'exécution de la détection des modifications. En d'autres termes, le rebond et la limitation n'affectent pas la fréquence d'exécution de la détection des modifications . (J'ai trouvé un commentaire GitHub de Tobias Bosch qui le confirme.) Vous pouvez le voir lorsque vous exécutez le plunker et vous voyez combien de fois ngDoCheck()est appelé lorsque vous tapez dans la zone de saisie ou redimensionnez la fenêtre. (Utilisez le bouton bleu "x" pour exécuter le plunker dans une fenêtre séparée pour voir les événements de redimensionnement.)

Une technique plus efficace consiste à créer vous-même des observables RxJS à partir des événements, en dehors de la "zone" d'Angular. De cette façon, la détection des modifications n'est pas appelée à chaque fois qu'un événement se déclenche. Ensuite, dans vos méthodes de rappel d'abonnement, déclenchez manuellement la détection de changement - c'est-à-dire que vous contrôlez le moment où la détection de changement est appelée:

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

J'utilise ngAfterViewInit()au lieu de ngOnInit()pour m'assurer que inputElRefc'est défini.

detectChanges()exécutera la détection des modifications sur ce composant et ses enfants. Si vous préférez exécuter la détection des modifications à partir du composant racine (c'est-à-dire exécuter une vérification complète de la détection des modifications), utilisez à la ApplicationRef.tick()place. (J'ai mis un appel à ApplicationRef.tick()dans les commentaires dans le plunker.) Notez que l'appel tick()provoquera ngDoCheck()un appel.


2
@Mark Rajcok Je pense qu'au lieu de [value], vous devriez utiliser [ngModel], car [value] ne met pas à jour la valeur d'entrée.
Milad

1
existe-t-il une méthode anti-rebond générique (par exemple à appliquer lors d'un événement de redimensionnement de la fenêtre)?
albanx le

1
@MarkRajcok Je crois que le problème du CD que vous avez décrit dans votre réponse est résolu par github.com/angular/zone.js/pull/843
Jefftopia

2
Quand aurions-nous besoin de nous désinscrire pour éviter les fuites de mémoire?
slanden

1
@slanden Oui, selon netbasal.com/when-to-unsubscribe-in-angular-d61c6b21bad3 , nous devrions nous désabonner des .fromEvent()abonnements
Jon Onstott

153

Si vous ne voulez pas vous en occuper @angular/forms, vous pouvez simplement utiliser un RxJS Subjectavec des liaisons de modification.

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

Cela déclenche la détection des changements. Pour un moyen qui ne déclenche pas la détection des modifications, consultez la réponse de Mark.


Mettre à jour

.pipe(debounceTime(300), distinctUntilChanged()) est nécessaire pour rxjs 6.

Exemple:

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

5
Je préfère cette solution! A travaillé avec angular 2.0.0, rxjs 5.0.0-beta 12
alsco77

2
Fonctionne parfaitement, simple et claire, aucune forme impliquée. Je suis sur Angular 4.1.3, rxjs 5.1.1
cinquième

Je pense que c'est une solution supérieure car elle a la possibilité de travailler avec des formulaires si nécessaire, mais supprime cette dépendance rendant la mise en œuvre beaucoup plus simple. Merci.
Max

2
.pipe(debounceTime(300), distinctUntilChanged())est nécessaire pour rxjs 6
Icycool

La solution m'a sauvé. J'utilisais l' keyUpévénement input.nativeElementdans un mat-table, qui a cessé de fonctionner lorsque le nombre de colonnes a été modifié
igorepst

35

Elle pourrait être mise en œuvre sous forme de directive

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

}

utilisez-le comme

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

échantillon de composant

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

1
avec plus d'importations, cela a fonctionné pour moi: import "rxjs / add / operator / debounceTime"; import "rxjs / add / opérateur / distinctUntilChanged";
Sbl

2
Cela en fait de loin le plus simple à mettre en œuvre à l'échelle de l'application
Joshcomley

1
isFirstChange est utilisé pour ne pas émettre à l'initialisation
Oleg Polezky

2
Fonctionne dans Angular 8 et rxjs 6.5.2 avec les modifications suivantes. Si vous souhaitez utiliser la syntaxe du tube, modifiez ce qui suit: import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged';to import { debounceTime, distinctUntilChanged } from 'rxjs/operators';et this.model.valueChanges .debounceTime(this.debounceTime) .distinctUntilChanged()tothis.model.valueChanges .pipe( debounceTime(this.debounceTime), distinctUntilChanged() )
kumaheiyama

1
Fonctionne dans Angular 9 et rxjs 6.5.4 avec les modifications indiquées par @kumaheiyama dans son commentaire. N'oubliez pas d'exporter la directive dans le module où vous la créez. Et n'oubliez pas d'inclure le module dans lequel vous créez cette directive, dans le module où vous l'utilisez.
Filip Savic le

29

Comme le sujet est ancien, la plupart des réponses ne fonctionnent pas sur Angular 6/7/8/9 et / ou utilisent d'autres bibliothèques.
Voici donc une solution courte et simple pour Angular 6+ avec RxJS.

Importez d'abord les éléments nécessaires:

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

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

Utilisez de cette façon:

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

PS: Pour des solutions plus complexes et efficaces, vous voudrez peut-être toujours vérifier d'autres réponses.


1
Pourquoi ne pas vous désabonner lors de la destruction?
Virendra Singh Rathore

Actualisé. Merci d'avoir remarqué!
Just Shadow

1
@JustShadow Merci! C'était vraiment utile.
Niral Munjariya

Cela fonctionne parfaitement du premier coup. Mais lorsque je supprime le texte recherché, la demande suivante prend trop de temps à répondre.
Sadiksha Gautam

C'est étrange. Cela fonctionne toujours bien de mon côté. Pourriez-vous s'il vous plaît partager plus d'informations ou peut-être ouvrir une nouvelle question à ce sujet?
Just Shadow

28

Pas directement accessible comme dans angular1 mais vous pouvez facilement jouer avec les observables NgFormControl et RxJS:

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

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

Ce billet de blog l'explique clairement: http://blog.oughttram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html

Ici, c'est pour une saisie semi-automatique mais cela fonctionne dans tous les scénarios.


mais il y a une erreur de service, cela ne fonctionne plus
Arun Tyagi

Je ne comprends pas l'exemple. [...] est une liaison cible unidirectionnelle. Pourquoi le conteneur peut-il être notifié valueChanges? ça ne devrait pas être qch. comme (ngFormControl)="..."?
phil294

20

Vous pouvez créer un RxJS (v.6) Observable qui fait ce que vous voulez.

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


}

Merci qui a aidé, mais je pense que l'importation devrait provenir rsjs/Rx, j'ai eu des erreurs lors de l'utilisation de l'importation comme vous l'avez écrite ... donc dans mon cas, c'est maintenant:import { Observable } from 'rxjs/Rx';
ghiscoding

2
@ghiscoding Cela dépend de la version rxjs. Dans la version 6 est: import { Observable } from 'rxjs';.
Matthias

Merci! En passant, vous ne pouvez utiliser qu'un seul pipeappelpipe(debounceTime(300), distinctUntilChanged())
al.

1
searchChangeObserver est un abonné, donc searchChangeSubscriber sera un meilleur nom.
Khonsort

12

Pour quiconque utilise lodash, il est extrêmement facile de supprimer n'importe quelle fonction:

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

puis lancez simplement quelque chose comme ça dans votre modèle:

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

3
ou juste (input) = "changed ($ event.target.value)"
Jamie Kudla

1
Merci d'avoir répondu avec lodash :)
Vamsi

Je crois que cela déclenchera toujours la détection de changement angulaire à chaque changement, quel que soit le anti-rebond.
AsGoodAsIt Obtient le

5

Solution avec abonné d'initialisation directement en fonction événement:

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

Et html:

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

Fonctionne parfaitement pour la zone de texte de saisie semi-automatique angulaire 8 prime ng. Merci beaucoup.
Jasmin Akther Suma

4

J'ai résolu ce problème en écrivant un décorateur anti-rebond. Le problème décrit peut être résolu en appliquant le @debounceAccessor à l'accesseur set de la propriété.

J'ai également fourni un décorateur anti-rebond supplémentaire pour les méthodes, qui peut être utile pour d'autres occasions.

Cela rend très facile la suppression du rebond d'une propriété ou d'une méthode. Le paramètre est le nombre de millisecondes que le anti-rebond doit durer, 100 ms dans l'exemple ci-dessous.

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


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

Et voici le code pour les décorateurs:

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

J'ai ajouté un paramètre supplémentaire pour le décorateur de méthode qui vous permet de déclencher la méthode APRÈS le délai anti-rebond. Je l'ai fait pour pouvoir par exemple l'utiliser lorsqu'il est associé à des événements de survol de la souris ou de redimensionnement, où je voulais que la capture se produise à la fin du flux d'événements. Dans ce cas cependant, la méthode ne retournera pas de valeur.


3

Nous pouvons créer une directive [debounce] qui écrase la fonction viewToModelUpdate par défaut de ngModel par une fonction vide.

Code de directive

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

Comment l'utiliser

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

2

Fichier HTML:

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

Fichier TS:

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

2

Une solution simple serait de créer une directive que vous pouvez appliquer à n'importe quel contrôle.

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

l'utilisation serait

<textarea [ngModel]="somevalue"   
          [debounce]="2000"
          (onDebounce)="somevalue = $event"                               
          rows="3">
</textarea>

Cette classe est loin d'être compilée Angular 7.
Stephane

1

J'ai passé des heures là-dessus, j'espère pouvoir faire gagner du temps à quelqu'un d'autre. Pour moi, l'approche suivante d'utilisation debouncesur un contrôle est plus intuitive et plus facile à comprendre pour moi. Il est basé sur la solution docs angular.io pour la saisie semi-automatique, mais avec la possibilité pour moi d'intercepter les appels sans avoir à dépendre de la liaison des données au DOM.

Plunker

Un scénario d'utilisation pour cela pourrait être de vérifier un nom d'utilisateur après sa saisie pour voir si quelqu'un l'a déjà pris, puis d'avertir l'utilisateur.

Remarque: n'oubliez pas, cela (blur)="function(something.value)peut avoir plus de sens pour vous en fonction de vos besoins.


1

DebounceTime dans Angular 7 avec RxJS v6

Lien source

Lien de démonstration

entrez la description de l'image ici

Dans un modèle HTML

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

Dans le composant

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

    }

1

Vous pouvez également résoudre ce problème en utilisant un décorateur, pour une instance en utilisant le décorateur anti- rebond de utils-decorator lib ( npm install utils-decorators):

import {debounce} from 'utils-decorators';

class MyAppComponent {

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

0

C'est la meilleure solution que j'ai trouvée jusqu'à présent. Met à jour le ngModelsur bluretdebounce

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

emprunté à https://stackoverflow.com/a/47823960/3955513

Puis en HTML:

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

Sur blurle modèle est explicitement mis à jour en utilisant du javascript brut.

Exemple ici: https://stackblitz.com/edit/ng2-debounce-working

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.