In AngularJS kann ich ein Modell mit den Optionen von ng-model entprellen.
ng-model-options="{ debounce: 1000 }"
Wie kann ich ein Modell in Angular entprellen? Ich habe versucht, in den Dokumenten nach Entprellung zu suchen, aber ich konnte nichts finden.
https://angular.io/search/#stq=debounce&stp=1
Eine Lösung wäre, meine eigene Debounce-Funktion 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?
Aktualisiert für RC.5
Mit Angular 2 können wir mithilfe des RxJS-Operators debounceTime()
auf den valueChanges
eines Formularsteuerelements debounce:
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();
}
}
Der obige Code enthält auch ein Beispiel für das Drosseln der Fenstergrößenänderung, wie von @albanx in einem Kommentar unten angefordert.
Obwohl der obige Code wahrscheinlich der Angular-Weg ist, ist er nicht effizient. Jeder Tastenanschlag und jedes Ereignis zur Größenänderung führt zur Ausführung der Änderungserkennung, auch wenn sie entwertet und gedrosselt werden. Mit anderen Worten, das Entprellen und Drosseln hat keinen Einfluss darauf, wie oft die Änderungserkennung ausgeführt wird . (Ich habe ein GitHub-Kommentar von Tobias Bosch gefunden, das 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 tippen oder die Größe ändern Fenster. (Verwenden Sie die blaue Schaltfläche "x", um den Plunker in einem separaten Fenster auszuführen und die Größenänderungsereignisse anzuzeigen.)
Eine effizientere Methode besteht darin, RxJS Observables außerhalb der "Zone" von Angular selbst aus den Ereignissen zu erstellen. Auf diese Weise wird die Änderungserkennung nicht bei jedem Auslösen eines Ereignisses aufgerufen. Lösen Sie dann in Ihren Methoden zum Abonnieren von Rückrufen die Änderungserkennung manuell aus, d. H. 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();
}
}
Ich verwende ngAfterViewInit()
anstelle von ngOnInit()
, um sicherzustellen, dass inputElRef
definiert ist.
detectChanges()
führt die Änderungserkennung für diese Komponente und ihre untergeordneten Komponenten aus. Wenn Sie die Änderungserkennung lieber über die Root-Komponente ausführen möchten (d. H. Eine vollständige Änderungserkennung durchführen), verwenden Sie stattdessen ApplicationRef.tick()
. (Ich habe in Kommentaren im Plunker ApplicationRef.tick()
angerufen.) Beachten Sie, dass durch das Aufrufen von tick()
ngDoCheck()
aufgerufen wird.
Wenn Sie sich nicht mit @angular/forms
beschäftigen möchten, können Sie einfach eine RxJS Subject
mit Änderungsbindungen verwenden.
<input [ngModel]='model' (ngModelChange)='changed($event)' />
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. Für einen Weg, der keine Änderungserkennung auslöst, checke Marks Antwort.
.pipe(debounceTime(300), distinctUntilChanged())
wird für rxjs 6 benötigt.
Beispiel:
constructor() {
this.modelChanged.pipe(
debounceTime(300),
distinctUntilChanged())
.subscribe(model => this.model = model);
}
Nicht direkt zugänglich wie in angle1, aber Sie können problemlos mit NgFormControl und RxJS-Observables spielen:
<input type="text" [ngFormControl]="term"/>
this.items = this.term.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(term => this.wikipediaService.search(term));
Dieser Blogbeitrag erklärt es deutlich: http://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html
Hier ist es für eine Autovervollständigung, aber es funktioniert in allen Szenarien.
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 gerne
<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);
});
}
}
Sie können create ein RxJS (v.6) Observable machen, das tut, was Sie möchten.
<input type="text" (input)="onSearchChange($event.target.value)" />
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);
}
}
Für jeden, der lodash verwendet, ist es extrem einfach, zu entprellen jede Funktion:
changed = _.debounce(function() {
console.log("name changed!");
}, 400);
dann werfen Sie einfach so etwas in Ihre Vorlage:
<input [ngModel]="firstName" (ngModelChange)="changed()" />
Ich löste dieses Problem, indem ich einen Entprägungsdekorateur schrieb. Das beschriebene Problem könnte gelöst werden, indem der @debounceAccessor auf den Set-Accessor der Eigenschaft angewendet wird.
Ich habe auch einen zusätzlichen Entprägungsdekorator 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 die Entprellung dauern soll, in dem 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 Method Decorator hinzugefügt, mit dem Sie die Methode NACH der Entprellungsverzögerung auslösen können. Ich habe das getan, damit ich es zum Beispiel in Verbindung mit Mouseover oder zur Größenänderung von Ereignissen verwenden konnte, wobei die Erfassung am Ende des Ereignisstroms erfolgen sollte. In diesem Fall gibt die Methode jedoch keinen Wert zurück.
Wir können eine Direktive [debounce] erstellen, die die standardmäßige viewToModelUpdate-Funktion von ngModel mit einer leeren Funktion ü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>
Eine einfache Lösung wäre die Erstellung einer Direktive, 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>
Da das Thema alt ist, funktionieren die meisten Antworten nicht auf Angular 6 .
Hier also eine kurze und einfache Lösung für Angular 6 mit RxJS.
Importiere zuerst notwendige Sachen:
import { Component, OnInit } from '@angular/core';
import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
Initialisierung bei ngOnInit
:
export class MyComponent implements OnInit {
notesText: string;
notesModelChanged: Subject<string> = new Subject<string>();
constructor() { }
ngOnInit() {
this.notesModelChanged
.pipe(
debounceTime(2000),
distinctUntilChanged()
)
.subscribe(newText => {
this.notesText = newText;
console.log(newText);
});
}
}
Verwenden Sie diesen Weg:
<input [ngModel]='notesText' (ngModelChange)='notesModelChanged.next($event)' />
P.S .: Für komplexere und effizientere Lösungen möchten Sie vielleicht noch andere Antworten prüfen.
Ich habe Stunden damit verbracht, hoffentlich kann ich jemandem etwas Zeit sparen. Für mich ist der folgende Ansatz zur Verwendung von debounce
in einer Steuerung intuitiver und verständlicher. Es basiert auf der angle.io docs-Lösung für Autocomplete, hat aber die Möglichkeit, Anrufe abzufangen, ohne dass die Daten an das DOM gebunden werden müssen.
Ein Anwendungsszenario für dieses Szenario besteht darin, einen Benutzernamen nach der Eingabe zu überprüfen, um festzustellen, ob er bereits vergeben ist. Anschließend wird der Benutzer gewarnt.
Hinweis: Vergessen Sie nicht, dass (blur)="function(something.value)
für Sie je nach Ihren Bedürfnissen sinnvoller ist.
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)
}
DebounceTime in Angular 7 mit RxJS v6
Quelle Link
Demo Link
In HTML-Vorlage
<input type="text" #movieSearchInput class="form-control"
placeholder="Type any movie name" [(ngModel)]="searchTermModel" />
In der 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)});
}
}
Dies ist die beste Lösung, die ich bisher gefunden habe. Aktualisiert die ngModel
on blur
und debounce
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 geliehen
Dann in HTML:
<input [(ngModel)]="hero.name"
[debounce]="3000"
(blur)="hero.name = $event.target.value"
(ngModelChange)="onChange()"
placeholder="name">
Bei blur
wird das Modell explizit mit einfachem Javascript aktualisiert.
Beispiel hier: https://stackblitz.com/edit/ng2-debounce-working
Lösung mit Initialisierungs-Teilnehmer direkt in Event-Funktion:
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)">