Winkelig und entprellen

160

In AngularJS kann ich ein Modell mithilfe von ng-model-Optionen entprellen.

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

Wie kann ich ein Modell in Angular entprellen? Ich habe versucht, in den Dokumenten nach Entprellungen zu suchen, konnte aber nichts finden.

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

Eine Lösung wäre, meine eigene Entprellungsfunktion zu schreiben, zum Beispiel:

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

Und mein HTML

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

Aber ich suche nach einer eingebauten Funktion, gibt es eine in Angular?

koningdavid
quelle
3
Dies könnte relevant sein, github.com/angular/angular/issues/1773 , anscheinend noch nicht implementiert.
Eric Martinez
Überprüfen Sie diesen Beitrag für Angular 7 mit RxJS v6 freakyjolly.com/…
Code Spy

Antworten:

202

Aktualisiert für RC.5

Mit Angular 2 können wir mit dem RxJS-Operator debounceTime()auf dem valueChangesObservable eines Formularsteuerelements entprellen :

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

Der obige Code enthält auch ein Beispiel für das Drosseln der Größe von Fenstern, wie von @albanx in einem Kommentar unten gefragt.


Obwohl der obige Code wahrscheinlich die Angular-Methode ist, ist er nicht effizient. Jeder Tastendruck und jedes Größenänderungsereignis führt dazu, dass die Änderungserkennung ausgeführt wird, obwohl sie entprellt und gedrosselt werden. Mit anderen Worten, das Entprellen und Drosseln hat keinen Einfluss darauf, wie oft die Änderungserkennung ausgeführt wird . (Ich habe einen GitHub-Kommentar von Tobias Bosch gefunden, der dies bestätigt.) Sie können dies sehen, wenn Sie den Plunker ausführen, und Sie sehen, wie oft ngDoCheck()aufgerufen wird, wenn Sie in das Eingabefeld eingeben oder die Fenstergröße ändern. (Verwenden Sie die blaue Schaltfläche "x", um den Plunker in einem separaten Fenster auszuführen und die Größenänderungsereignisse anzuzeigen.)

Eine effizientere Technik besteht darin, RxJS Observables selbst aus den Ereignissen außerhalb von Angulars "Zone" zu erstellen. Auf diese Weise wird die Änderungserkennung nicht jedes Mal aufgerufen, wenn ein Ereignis ausgelöst wird. Lösen Sie dann in Ihren Abonnement-Rückrufmethoden die Änderungserkennung manuell aus, dh Sie steuern, wann die Änderungserkennung aufgerufen wird:

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

Ich benutze ngAfterViewInit()statt ngOnInit()um sicherzustellen, dass inputElRefdas definiert ist.

detectChanges()führt die Änderungserkennung für diese Komponente und ihre untergeordneten Komponenten aus. Wenn Sie die Änderungserkennung lieber über die Stammkomponente ausführen möchten (dh eine vollständige Änderungserkennungsprüfung durchführen), verwenden Sie ApplicationRef.tick()stattdessen. (Ich habe ApplicationRef.tick()in Kommentaren im Plunker einen Anruf getätigt.) Beachten Sie, dass beim Aufruf ein Anruf ausgelöst tick()wird ngDoCheck().

Mark Rajcok
quelle
2
@ Mark Rajcok Ich denke, anstelle von [Wert] sollten Sie [ngModel] verwenden, da [Wert] den Eingabewert nicht aktualisiert.
Milad
1
Gibt es eine generische Entprellungsmethode (z. B. zum Anwenden auf ein Fenstergrößenänderungsereignis)?
Albanx
1
@ MarkRajcok Ich glaube, das CD-Problem, das Sie in Ihrer Antwort beschrieben haben, wird von github.com/angular/zone.js/pull/843
Jefftopia
2
Wann müssen wir uns abmelden, um Speicherverluste zu vermeiden?
Slanden
1
@slanden Ja, laut netbasal.com/when-to-unsubscribe-in-angular-d61c6b21bad3 sollten wir uns von .fromEvent()Abonnements abmelden
Jon Onstott
152

Wenn Sie sich nicht damit befassen möchten @angular/forms, können Sie einfach ein RxJS Subjectmit Änderungsbindungen verwenden.

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

Dies löst eine Änderungserkennung aus. Lesen Sie Marks Antwort, um eine Möglichkeit zu finden, die keine Änderungserkennung auslöst.


Aktualisieren

.pipe(debounceTime(300), distinctUntilChanged()) wird für rxjs 6 benötigt.

Beispiel:

   constructor() {
        this.modelChanged.pipe(
            debounceTime(300), 
            distinctUntilChanged())
            .subscribe(model => this.model = model);
    }
0xcaff
quelle
5
Ich bevorzuge diese Lösung! Arbeitete mit Angular 2.0.0, RXJS 5.0.0-Beta 12
alsco77
2
Arbeitete perfekt, einfach und klar, ohne Form. Ich bin auf Angular 4.1.3, rxjs 5.1.1
fünfter
Ich denke, dies ist eine überlegene Lösung, da sie die Möglichkeit bietet, bei Bedarf mit Formularen zu arbeiten, diese Abhängigkeit jedoch beseitigt, wodurch die Implementierung viel einfacher wird. Vielen Dank.
Max
2
.pipe(debounceTime(300), distinctUntilChanged())wird für rxjs 6
Icycool
Die Lösung hat mich gerettet. Ich habe keyUpevent on input.nativeElementin a verwendet mat-table, das nicht mehr funktioniert, als die Anzahl der Spalten geändert wurde
igorepst
35

Es könnte als Richtlinie umgesetzt werden

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

}

benutze es wie

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

Komponentenprobe

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
quelle
1
Bei mehr Importen hat das bei mir funktioniert: import "rxjs / add / operator / debounceTime"; import "rxjs / add / operator / uniqueUntilChanged";
Sbl
2
Dies macht es bei weitem am einfachsten, anwendungsweit zu implementieren
Joshcomley
1
isFirstChange wird verwendet, um beim Initialisieren nicht zu emittieren
Oleg Polezky
2
Funktioniert in Angular 8 und rxjs 6.5.2 mit den folgenden Änderungen. Wenn Sie die Pipe-Syntax verwenden möchten, ändern Sie Folgendes: import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged';to import { debounceTime, distinctUntilChanged } from 'rxjs/operators';und this.model.valueChanges .debounceTime(this.debounceTime) .distinctUntilChanged()tothis.model.valueChanges .pipe( debounceTime(this.debounceTime), distinctUntilChanged() )
kumaheiyama
1
Funktioniert in Angular 9 und rxjs 6.5.4 mit Änderungen, die @kumaheiyama in seinem Kommentar angegeben hat. Vergessen Sie nicht, die Direktive in das Modul zu exportieren, in dem Sie sie erstellen. Vergessen Sie nicht, das Modul, in dem Sie diese Direktive erstellen, in das Modul aufzunehmen, in dem Sie es verwenden.
Filip Savic
29

Da das Thema alt ist, funktionieren die meisten Antworten nicht in Angular 6/7/8/9 und / oder verwenden andere Bibliotheken.
Hier ist eine kurze und einfache Lösung für Angular 6+ mit RxJS.

Importieren Sie zuerst das notwendige Material:

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

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

Verwenden Sie diesen Weg:

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

PS: Für komplexere und effizientere Lösungen möchten Sie möglicherweise noch andere Antworten überprüfen.

Nur Schatten
quelle
1
Warum melden Sie sich nicht bei destroy ab?
Virendra Singh Rathore
Aktualisiert. Danke fürs bemerken!
Nur Schatten
1
@ JustShadow Danke! Es war sehr hilfreich.
Niral Munjariya
Dies funktioniert perfekt beim ersten Versuch. Aber wenn ich den gesuchten Text irgendwie lösche, dauert die Antwort der nächsten Anfrage zu lange.
Sadiksha Gautam
Das ist seltsam. Es funktioniert immer noch gut auf meiner Seite. Könnten Sie bitte weitere Informationen teilen oder eine neue Frage dazu stellen?
Nur Schatten
28

Nicht direkt zugänglich wie in angle1, aber Sie können problemlos mit NgFormControl- und RxJS-Observablen spielen:

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

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

Dieser Blog-Beitrag erklärt es klar: http://blog.ehowtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html

Hier ist es für eine automatische Vervollständigung, aber es funktioniert in allen Szenarien.

bertrandg
quelle
aber es gibt einen Fehler vom Dienst, dieser läuft nicht wieder
Arun Tyagi
Ich verstehe das Beispiel nicht. [...] ist eine Einweg-Zielbindung. Warum kann der Container benachrichtigt werden valueChanges? sollte es nicht etw sein müssen wie (ngFormControl)="..."?
Phil294
20

Sie können ein RxJS (v.6) Observable erstellen , das macht, was Sie wollen.

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
quelle
Vielen Dank, das hat geholfen, aber ich denke, der Import sollte von sein rsjs/Rx. Ich hatte Fehler, als ich den Import so verwendete, wie Sie ihn geschrieben haben. In meinem Fall ist es jetzt:import { Observable } from 'rxjs/Rx';
Ghiscoding
2
@ghiscoding Es hängt von der rxjs-Version ab. In Version 6 ist es : import { Observable } from 'rxjs';.
Matthias
Vielen Dank! Als beiseite, können Sie nur einen verwenden pipeAnrufpipe(debounceTime(300), distinctUntilChanged())
al.
1
searchChangeObserver ist ein Abonnent, daher ist searchChangeSubscriber ein besserer Name.
Khonsort
12

Für jeden, der lodash verwendet, ist es extrem einfach, eine Funktion zu entprellen :

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

dann werfen Sie einfach so etwas in Ihre Vorlage:

<(input)="changed($event.target.value)" />
Brad C.
quelle
3
oder einfach (Eingabe) = "geändert ($ event.target.value)"
Jamie Kudla
1
Vielen Dank für die Antwort mit lodash :)
Vamsi
Ich glaube, dies wird immer noch die Erkennung von Winkeländerungen bei jeder einzelnen Änderung auslösen, unabhängig vom Entprellen.
AsGoodAsItGets
5

Lösung mit Initialisierungsteilnehmer direkt in der Ereignisfunktion:

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

Und html:

<input type="text" (input)="onFind($event.target.value)">
Serhii Vasko
quelle
Funktioniert völlig gut für eckige 8-Primer-Textfelder zur automatischen Vervollständigung. Vielen Dank.
Jasmin Akther Suma
4

Ich habe das gelöst, indem ich einen Debounce-Dekorateur geschrieben habe. Das beschriebene Problem kann gelöst werden, indem der @debounceAccessor auf den festgelegten Accessor der Eigenschaft angewendet wird.

Ich habe auch einen zusätzlichen Debounce-Dekorator für Methoden bereitgestellt, der für andere Gelegenheiten nützlich sein kann.

Dies macht es sehr einfach, eine Eigenschaft oder eine Methode zu entprellen. Der Parameter gibt die Anzahl der Millisekunden an, die das Entprellen dauern soll, im folgenden Beispiel 100 ms.

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


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

Und hier ist der Code für die Dekorateure:

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

Ich habe einen zusätzlichen Parameter für den Methodendekorator hinzugefügt, mit dem Sie die Methode NACH der Entprellungsverzögerung auslösen können. Ich habe das getan, damit ich es beispielsweise in Verbindung mit Mouseover- oder Größenänderungsereignissen verwenden kann, bei denen die Erfassung am Ende des Ereignisstroms erfolgen soll. In diesem Fall gibt die Methode jedoch keinen Wert zurück.

Fredrik_Macrobond
quelle
3

Wir können eine [entprellen] Direktive erstellen, die die Standardfunktion viewToModelUpdate von ngModel mit einer leeren überschreibt.

Richtliniencode

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

Wie man es benutzt

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

HTML-Datei:

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

TS-Datei:

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

Eine einfache Lösung wäre, eine Direktive zu erstellen, die Sie auf jedes Steuerelement anwenden können.

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

Nutzung wäre

<textarea [ngModel]="somevalue"   
          [debounce]="2000"
          (onDebounce)="somevalue = $event"                               
          rows="3">
</textarea>
Ashg
quelle
Diese Klasse ist weit davon entfernt, kompiliert zu werden Angular 7.
Stephane
1

Ich habe Stunden damit verbracht, hoffentlich kann ich jemand anderem etwas Zeit sparen. Für mich ist der folgende Ansatz zur Verwendung debounceeines Steuerelements intuitiver und für mich leichter zu verstehen. Es basiert auf der Angular.io Docs-Lösung für die automatische Vervollständigung, bietet mir jedoch die Möglichkeit, die Anrufe abzufangen, ohne die Daten an das DOM binden zu müssen.

Plunker

Ein Anwendungsszenario hierfür könnte darin bestehen, einen Benutzernamen nach der Eingabe zu überprüfen, um festzustellen, ob jemand ihn bereits übernommen hat, und dann den Benutzer zu warnen.

Hinweis: Vergessen Sie nicht, (blur)="function(something.value)dass dies je nach Ihren Anforderungen für Sie möglicherweise sinnvoller ist.

Helzgate
quelle
1

DebounceTime in Angular 7 mit RxJS v6

Quelle Link -

Demo Link

Geben Sie hier die Bildbeschreibung ein

In der HTML-Vorlage

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

In Komponente

    ....
    ....
    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
quelle
1

Sie können dies auch mithilfe eines Dekorators lösen. Beispiel: Verwenden Sie den Debounce-Dekorator von utils-decorator lib ( npm install utils-decorators):

import {debounce} from 'utils-decorators';

class MyAppComponent {

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

Dies ist die beste Lösung, die ich bisher gefunden habe. Aktualisiert die ngModelauf blurunddebounce

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

wie von https://stackoverflow.com/a/47823960/3955513 ausgeliehen

Dann in HTML:

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

Das blurModell wird explizit mit einfachem Javascript aktualisiert.

Beispiel hier: https://stackblitz.com/edit/ng2-debounce-working

Shyamal Parikh
quelle