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?

koningdavid
la source
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.

Mark Rajcok
la source
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);
    }
0xcaff
la source
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);
    });
  }
} 
Oleg Polezky
la source
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.

Juste l'ombre
la source
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.

bertrandg
la source
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);
  }  


}
Matthias
la source
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)" />
Brad C
la source
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)">
Serhii Vasko
la source
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.

Fredrik_Macrobond
la source
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>
BebbaPig
la source
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)
  }
Yoav Schniederman
la source
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>
Ashg
la source
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.

Helzgate
la source
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)});
    }

    }
Espion de code
la source
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) {
   ...
  }
}
vlio20
la source
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

Shyamal Parikh
la source