Angular + debounceTime
December 31, 2018
debounceTime
is a RxJS operator that emits latest value from the source Observable
after a given time span has passed without another source emission. It behaves same as RxJS delay
but emits only the latest value. debounceTime
delays the values emitted by source Observable
for the given due time and within this time if a new value arrives, the previous pending value is dropped. In this way debounceTime
keeps track of most recent value and emits that most recent value when the given due time is passed. debounceTime
is an RxJS operator declared as following.
debounceTime(dueTime: number, scheduler: Scheduler): Observable
debounceTime
is imported as following.
import { debounceTime } from 'rxjs/operators';
pipe
operator of Observable
. debounceTime
is useful in operation where user changes inputs frequently such as search operation.
Contents
Technologies Used
Find the technologies being used in our example.1. Angular 7.0.0
2. Angular CLI 7.0.3
3. TypeScript 3.1.1
4. Node.js 10.3.0
5. NPM 6.1.0
6. RxJS 6.3.3
7. In-Memory Web API 0.6.1
Understanding debounceTime
Suppose we have a simple form with an input field to take input from user to search a book.<form [formGroup]="bookForm"> ID: <input formControlName="bookId"> </form>
this.bookId.valueChanges.pipe( tap(val => console.log(val)) ).subscribe(data => this.userInput = data);
valueChanges
that returns Observable
instance.
If we want to process latest user input with a certain time gap from the time the last user input processing has taken place, and not for every user input change, then we can use RxJS
debounceTime
as following.
this.bookId.valueChanges.pipe( debounceTime(2000), tap(val => console.log(val)) ).subscribe(data => this.userInput = data);
tap
will get latest user input only after 2 seconds from time the previous user input has processed. This is because debounceTime
will not emit value for the given due time. Once debounceTime
has emitted value then next value will be emitted only after 2 seconds with latest value in the case user is continuously entering data into input box.
Using debounceTime with switchMap
Suppose we want to search book for the keyword entered by user. It is possible that user may change keyword frequently and in that case every time search operation will be performed. This operation may be costly because it may involve HTTP hit, database hit etc. Suppose we want search operation on change input value and user wants to search for book id. To avoid frequent hit, we may usedebounceTime
as following.
this.bookId.valueChanges.pipe( debounceTime(1000), switchMap(id => { console.log(id); return this.bookService.getBook(id); }) ).subscribe(res => this.book = res);
Using debounceTime with fromEvent
fromEvent
creates an Observable
instance that emits events of given type coming from the given event target. In our example we have a <div> element. We will catch mouse over event.
fromEvent(document.getElementById('myelement'), 'mouseover').pipe( debounceTime(1000), map(data => data.srcElement) ).subscribe(val => console.log(val));
<div id="myelement"> Mouse over </div>
fromEvent
code executes. It is possible that we may perform mouse over operation multiple times in a very short span of time and hence complete process will execute for that number of times. To avoid unnecessary execution, we can use debounceTime
. In above code we have used debounceTime
with 1 second due time. For two consecutive mouse over operation, only after 1 second the code will execute if the code has executed already.
Complete Example
book.component.tsimport { Component, OnInit } from '@angular/core'; import { fromEvent } from 'rxjs'; import { switchMap, debounceTime, tap, map } from 'rxjs/operators'; import { BookService } from './book.service'; import { Book } from './book'; import { FormControl, FormBuilder, FormGroup } from '@angular/forms'; @Component({ selector: 'app-book', template: ` <div id="myelement"> Mouse over </div> <h3>Search Book By Id</h3> <form [formGroup]="bookForm"> ID: <input formControlName="bookId"> </form> <div *ngIf="book"> Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}} </div> ` }) export class BookComponent implements OnInit { book: Book; constructor(private bookService: BookService, private formBuilder: FormBuilder) { } ngOnInit() { this.searchBook(); fromEvent(document.getElementById('myelement'), 'mouseover').pipe( debounceTime(1000), map(data => data.srcElement) ).subscribe(val => console.log(val)); } bookId = new FormControl(); bookForm: FormGroup = this.formBuilder.group({ bookId: this.bookId }); searchBook() { this.bookId.valueChanges.pipe( debounceTime(1000), switchMap(id => { console.log(id); return this.bookService.getBook(id); }) ).subscribe(res => this.book = res); } }
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, of } from 'rxjs'; import { Book } from './book'; import { catchError } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class BookService { bookUrl = "/api/books"; constructor(private http: HttpClient) { } getBook(id: number): Observable<Book> { return this.http.get<Book>(this.bookUrl + "/" + id).pipe( catchError(err => of(null)) ); } }
export interface Book { id: number; name: string; category: string; }
import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <app-book></app-book> ` }) export class AppComponent { }
import { InMemoryDbService } from 'angular-in-memory-web-api'; export class TestData implements InMemoryDbService { createDb() { let bookDetails = [ { id: 101, name: 'Angular by Krishna', category: 'Angular' }, { id: 102, name: 'Core Java by Vishnu', category: 'Java' }, { id: 103, name: 'NgRx by Rama', category: 'Angular' } ]; return { books: bookDetails }; } }
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { AppComponent } from './app.component'; import { BookComponent } from './book.component'; //For InMemory testing import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; import { TestData } from './test-data'; @NgModule({ imports: [ BrowserModule, HttpClientModule, FormsModule, ReactiveFormsModule, InMemoryWebApiModule.forRoot(TestData) ], declarations: [ AppComponent, BookComponent ], providers: [ ], bootstrap: [ AppComponent ] }) export class AppModule { }
Run Application
To run the application, find the steps.1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Install angular-in-memory-web-api@0.6.1
4. Run ng serve using command prompt.
5. Access the URL http://localhost:4200
References
RxJS ObservableAngular: The RxJS library
Pipeable Operators