Angular 2+ and debounce
Updated for RC.5
With Angular 2 we can debounce using RxJS operator debounceTime()
on a form control's valueChanges
observable:
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
The code above also includes an example of how to throttle window resize events, as asked by @albanx in a comment below.
Although the above code is probably the Angular-way of doing it, it is not efficient. Every keystroke and every resize event, even though they are debounced and throttled, results in change detection running. In other words, debouncing and throttling do not affect how often change detection runs. (I found a GitHub comment by Tobias Bosch that confirms this.) You can see this when you run the plunker and you see how many times ngDoCheck()
is being called when you type into the input box or resize the window. (Use the blue "x" button to run the plunker in a separate window to see the resize events.)
A more efficient technique is to create RxJS Observables yourself from the events, outside of Angular's "zone". This way, change detection is not called each time an event fires. Then, in your subscribe callback methods, manually trigger change detection – i.e., you control when change detection is called:
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
I use ngAfterViewInit()
instead of ngOnInit()
to ensure that inputElRef
is defined.
detectChanges()
will run change detection on this component and its children. If you would rather run change detection from the root component (i.e., run a full change detection check) then use ApplicationRef.tick()
instead. (I put a call to ApplicationRef.tick()
in comments in the plunker.) Note that calling tick()
will cause ngDoCheck()
to be called.
debounce to get a value input in angular2
Well, there is lot's of ways to achieve that, but here is one way :
<input *ngIf="edit" type="text" #input="ngModel" [(ngModel)]="card.title" (ngModelChange)='rename()'/>
And inside your class
newTitle : string;
@ViewChild('input') input;
constructor()
}
ngAfterViewInit(){
this.input.valueChanges
.pipe(debounceTime(500)) before emitting last event
.pipe(distinctUntilChanged())
.subscribe(model => (value)=>{
console.log('delayed key press value',value);
this.rename(value)
});
}
rename(value): void {
this.renameRequest.emit(value);
}
Here is the Plunker
You can even subscribe to modelChange like bellow :
ngAfterViewInit(){
this.input.update // this is (modelChange)
.pipe(debounceTime(500)) before emitting last event
.pipe(distinctUntilChanged())
.subscribe(model => (value)=>{
console.log('delayed key press value',value);
});
}
Angular - Add debounce to an event
Add a template reference variable on your button :
<button #button>Click me</button>
Reference it inside your component using @ViewChild
@ViewChild('button') button: ElementRef;
Use fromEvent from rxjs to listen to the click event and use the debounceTime operator :
ngAfterViewInit() {
fromEvent(this.button.nativeElement, 'click').pipe(
debounceTime(2000) // 2 seconds
).subscribe(() => {
// do whatever
});
}
Angular 2: Debounce (ngModelChange)?
EDIT
In new version of Angular you can use updateOn
in ngModelOption
to set 'blur'
for example. Link to angular.io documentation.
Code example :
<input [(ngModel)]="value"
[ngModelOptions]="{ updateOn: 'blur' }"
(ngModelChange)="updateOnlyOnBlur($event)">
LEGACY
Here's the less painful way of debouncing keystrokes if you don't want to use the formcontrol
approach.
search.component.html
<input type="text" placeholder="Enter a value" name="foo" [(ngModel)]="txtQuery" (ngModelChange)="onFieldChange($event)">
search.component.ts
export class SearchComponent {
txtQuery: string; // bind this to input with ngModel
txtQueryChanged: Subject<string> = new Subject<string>();
constructor() {
this.txtQueryChanged
.debounceTime(1000) // wait 1 sec after the last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(model => {
this.txtQuery = model;
// Call your function which calls API or do anything you would like do after a lag of 1 sec
this.getDataFromAPI(this.txtQuery);
});
}
onFieldChange(query:string){
this.txtQueryChanged.next(query);
}
}
Angular 2 - Debouncing a keyUp event
UPDATE:
Using RXJS 6 pipe operator:
this.subject.pipe(
debounceTime(500)
).subscribe(searchTextValue => {
this.handleSearch(searchTextValue);
});
You could create a rxjs/Subject and call .next() on keyup and subscribe to it with your desired debounceTime.
I'm not sure if it is the right way to do it but it works.
private subject: Subject<string> = new Subject();
ngOnInit() {
this.subject.debounceTime(500).subscribe(searchTextValue => {
this.handleSearch(searchTextValue);
});
}
onKeyUp(searchTextValue: string){
this.subject.next(searchTextValue);
}
HTML:
<input (keyup)="onKeyUp(searchText.value)">
debounce angular 2 with ngModelChange
@Component({
selector: 'my-app',
template: `
<div>
<input type="text" (keyup)='keyUp.next($event)'>
</div>
`,
})
export class App {
name:string;
public keyUp = new Subject<string>();
constructor() {
const observable = this.keyUp
.map(value => event.target.value)
.debounceTime(1000)
.distinctUntilChanged()
.flatMap((search) => {
return Observable.of(search).delay(500);
})
.subscribe((data) => {
console.log(data);
});
}
}
Angular 9 how to use debouncetime for input change event (keyup)
A form control will expose the changes in its values as an observable named valueChanges
. You're going to need to use the pipe
operator, piping valueChanges
into it. You can't just call debounceTime
like you are doing above.
You're also going to want to use something like a switchMap
to ensure that any in-flight requests are cancelled if a user types something while a request is in progress.
Without seeing your code this is the best I can really do.
Try using a formControl.
html;
<input matInput [formControl]='myControl'>
In your ts, declare this as a class variable:
myControl = new FormControl();
Then pipe the changes from it like this (can go in ngOnInit
):
this.myControl.valueChanges.pipe(
debounceTime(500),
switchMap(changedValue => this.productService.search_Products(changedValue)),
).subscribe(productList => this.product_list = productList);
Related Topics
Jquery Checkbox Change and Click Event
How to Implement Authenticated Routes in React Router 4
In JavaScript, How to Search an Array for a Substring Match
How to "Await" for a Callback to Return
How to Record Webcam and Audio Using Webrtc and a Server-Based Peer Connection
How to Do a Horizontal Scroll on Mouse Wheel Scroll
How to Add Drop-Down List (<Select>) Programmatically
Passing Variables Through Handlebars Partial
How to Play a Video in a Webview with Android
How to Get the Function Name from Within That Function
Get Hours Difference Between Two Dates in Moment Js
Memory Leak Risk in JavaScript Closures
When Is the Comma Operator Useful
How to Access the Contents of an Svg File in an <Img> Element