Angular + RxJS - debounce

By Arvind Rai, February 04, 2024
On this page we will learn to use RxJS debounce operator in our Angular application. debounce operator is used to delay the emission from source observable and emits only the latest notification. The waiting time of debounce is decided by another observable. When we use debounce, the notification from source observable emits only after a time span determined by another observable has passed without another source emission.
Here I will discuss debounce in detail with examples.

1. debounce

Find the debounce operator construct from RxJS API.
debounce<T>(durationSelector: (value: T) => 
          ObservableInput<any>): MonoTypeOperatorFunction<T> 
Parameters :
durationSelector is a Promise or an Observable to decide the waiting time. durationSelector is a function that receives a value from source observable to compute timeout duration for each source value.

Returns :
debounce operator returns an Observable which emits after a delay specified by observable returned by durationSelector function.

1. When source observable emits notification, debounce operator keeps it in waiting for due time and if another notification is emitted by source observable before waiting time of previous notification is finished, then previous notification is discarded and latest notification start waiting for the due time decided by durationSelector.
2. debounce operator keeps only latest notification from the source observable and spawns duration observable by calling durationSelector. The debounce operator will emit notification only when the duration observable emits a next notification and no notification was emitted since the duration observable was spawned.
3. If source observable emits a new notification before the duration observable emits a notification, then the previous notification is discarded and a new duration will be scheduled by duration observable.
4. If the completing event happens during the scheduled duration, the last cached notification emits before the completion event is forwarded to output observable and if error event happens during the scheduled duration, only the error event is forwarded.

2. Using debounce

For debounce operator demo, I am creating a FormControl instance and binding it to HTML input text element. Using valueChanges, I will get values entered by user. valueChanges will emit values for every change in input text.
Here our source observable is valueChanges that will emit notification on every value change in input box.
I am creating duration observable using RxJS interval operator that creates an observable to emit sequential numbers every specified interval of time.
HTML :
<input [formControl]="msg"> 
TS :
msg = new FormControl();
ngOnInit() {
   this.msg.valueChanges.pipe(
      debounce(v => interval(Math.random() * 3000))
   ).subscribe(v => {
      console.log(v);
   })
} 
For every value change by user, interval is creating new duration observable.
Suppose Math.random() returns 0.5 then interval will get specified time as 0.5 * 3000 = 1500 ms. The interval(1500) creates duration observable of 1500 ms. Within this period all the values entered by user in input text, will be discard and only the last one will be kept that will be emitted once 1500 ms is passed after last value change by user.

3. debounce vs debounceTime

1. debounce and debounceTime both operators are used to delay source emit but they differ in the way delay time is provided.
2. Delay time in debounceTime is a specified number whereas the delay time in debounce is obtained by another observable.
3. debounce and debounceTime both are rate-limiting and delay-like operators because they do not emit necessarily at the same time when source observable emits.
4. Find the code with debounceTime.
this.msg.valueChanges.pipe(
   debounceTime(1000)
).subscribe(v => {
   console.log(v);
}) 
5. Find the code with debounce.
this.msg.valueChanges.pipe(
   debounce(v => interval(1000))
).subscribe(v => {
   console.log(v);
}) 

4. debounce + switchMap Example

RxJS switchMap emits only latest inner observable in the case when before completing an inner observable new inner observable starts executing. Using debounce we can avoid creating inner observable by switchMap for every user input change. If inner observable is hitting HTTP request, that cost will be reduced by using debounce operator.
Here in our example, we have an application to fetch employee detail. User will enter employee id and employee detail will be displayed. Using debounce we can avoid HTTP hit for every input, for example suppose user want to enter employee id 111 then user will enter 1, 11, 111 and then he will stop. So for 1 and 11 values there should not be HTTP hit that we can achieve by using debounce operator.

Find the code.
emp.component.ts
import { Component, OnInit } from '@angular/core';
import { EmpService } from './services/emp.service';
import { CommonModule } from '@angular/common';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { Observable, debounce, interval, switchMap } from 'rxjs';

@Component({
   selector: 'my-app',
   standalone: true,
   imports: [CommonModule, ReactiveFormsModule],
   templateUrl: './emp.component.html'
})
export class EmpComponent implements OnInit {
   empId = new FormControl();
   constructor(private empService: EmpService) {
   }
   result$!: Observable<any[]>;
   ngOnInit() {
      this.result$ = this.empId.valueChanges.pipe(
         debounce(() => interval(1000)),
         switchMap(id => this.empService.findEmpById(id))
      );
   }
} 
emp.component.html
<input [formControl]="empId">
<br/><br/>
<div *ngIf="result$ | async as emp">
    {{emp | json}}
</div> 
emp.service.ts
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

@Injectable({
    providedIn: 'root'
})
export class EmpService {
    constructor(private http: HttpClient) { }
    findEmpById(id: number): Observable<any[]> {
        return this.http.get<any[]>("/api/getEmpById", {
            params: new HttpParams().set('id', id)
        });
    }
} 
test-data.ts
import { InMemoryDbService } from 'angular-in-memory-web-api';

export class TestData implements InMemoryDbService {
  createDb() {
    let empDetails = [
      { id: '111', name: 'Ritesh', city: "Varanasi" },
      { id: '222', name: 'Suresh', city: "PrayagRaj" }
    ];
    return { getEmpById: empDetails };
  }
} 
Find the print-screen of the output.
Angular + RxJS - debounce

5. Reference

6. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE






©2024 concretepage.com | Privacy Policy | Contact Us