Angular 15 DebounceTime Search with HTTP API Results Example by Tutorial

In this post, we’ll discuss how to add an Autocomplete, Suggestion control in Angular application and control the search behaviour using RxJS operators like debounceTime and distinctUntilChanged

This application will fetch remote server responses using a third-party API by using the HttpClientModule of Angular to make HTTP calls. By using the RxJS library we’ll control server API hits a user makes while communicating with the server limiting the network resources to optimize the client application.

Many real-world applications have search features that filter data fetched from a server or connected to some third-party API. In a normal practice a user types in the query to get related results in response. But in a technical sense if we bind a keyup event on an input search, then on every keyup event an API hit will be made for example if the user types in “car” then three API hits for each character will be made.

This type of behaviour on search inputs can affect application performance a lot as an API hit will be made on every key event to the server.

 

How to optimize the search?

Instead of making a server hit on every key event, we can allow a user to type a whole meaningful keyword to make a search for. But how do we know when a user is done? Well, we can set a time that can reset again after a user hits a key, again when a user hits the key it will reset. When a user stops typing this time will up, then we can hit a server API call.

Let’s do this programmatically…

Debounce Time

Debounce Time is the delay which we can add between event subscriptions. We can add Debounce Time of 1000 milliseconds which resets after every KeyUp event by a user, if the gap of time between KeyUp events exceeds 1000 ms then we make a subscription or make an API call.

 

Let’s implement Debounce in the Angular 15 application

We can achieve Debounce behavior in Angular application using React JS operators. Here in the demo application, we will have a search bar to find movies using free IMDB API services.

 

 

How to Add DebouceTime Search in Angular App?

Follow these quick steps to implement remove server search in the Angular app using the RxJS DebounceTime with other functions to optimize the search experience for users:

Step 1 – Setup Angular CLI
Step 2 – Create an Angular Project
Step 3 – Install Bootstrap Library
Step 4 – Add Search Template with Card
Step 5 – Update the App Component

 

 

Step 1 – Setup Angular CLI

Before creating a new Angular application, your system must have Angular CLI installed. For that execute the following command to update/ install the latest version of ng cli tool on your system.

$ npm install -g @angular/cli

 

Step 2 – Create an Angular Project

After installing the ng cli, we can easily create a new Angular application with the latest version of Angular version by hitting the following command in the terminal window:

ng new angular-debounce-search

 

Now, enter the application directory by running the below cd command:

cd angular-debounce-search

 

Step 3 – Install Bootstrap Library

We will be using the bootstrap classed in our application to easily style it. For that you need to first install the bootstrap library in your application:

npm install bootstrap

 

Now open the angular.json file in your application root folder, and add the bootstrap.min.css file in the styles array as shown below:

...
    "styles": [
      "node_modules/bootstrap/dist/css/bootstrap.min.css",
      "src/styles.css"
    ],
...

 

Step 4 – Add Search Template with Card

Our sample application with debounceTime() function will have a search input field and card component in the HTML template. In the Input form fields user will enter the search term based on that the dynamic search results will be fetched from a remote server and rendered into the cards component styles by bootstrap classes.

Open the app.component.html file and update as shown below:

<div class="container text-center">
  <div class="row">
    <div class="col-12">
      <h1>Angular Search using Debounce in RxJs</h1>
      <input type="text" #movieSearchInput class="form-control" placeholder="Type any movie name" />
    </div>
  </div>
  <div class="row" *ngIf="isSearching">
    <div class="col-12">
      <h4>Searching ... </h4>
    </div>
  </div>
  <div class="row">
    <ng-container *ngIf="apiResponse['Response'] == 'False'; else elseTemplate">
      <div class="col-12">
        <div class="alert alert-danger" role="alert">
          {{apiResponse['Error']}}
        </div>
      </div>
    </ng-container>
    <ng-template #elseTemplate>
      <div class="col-3" *ngFor="let movie of apiResponse['Search']">
        <div class="card card-margin">
          <img class="card-img-top" src="{{movie['Poster']}}">
          <div class="card-body">
            <h5 class="card-title">{{movie['Title']}}</h5>
            <p class="card-text">Year: {{movie['Year']}}</p>
          </div>
        </div>
      </div>
    </ng-template>
  </div>
</div>

In the template HTML to make search, we have an Input field control with a template variable #movieSearchInput to bind keyUp event using RxJs fromEvent method.

The list of searched movies will be iterated using the Angular *ngFor directive creating bootstrap cards for each movie. The isSearching boolean flag show "Searching..." message will show up indicating API hit progress.

 

Step 5 – Update the App Component

Thereafter, we will update the App Component class, which will take the input instance using the @ViewChild using that we will watch the change events to fetch the remote results by making HTTP calls.

Open the app.component.ts file and update as follows:

import { Component, ViewChild, ElementRef, OnInit } from '@angular/core';
import { of, throwError } from 'rxjs';
import {
  debounceTime,
  map,
  distinctUntilChanged,
  filter,
  catchError,
  switchMap,
} from 'rxjs/operators';
import { fromEvent } from 'rxjs';
import { HttpClient, HttpParams } from '@angular/common/http';

const APIKEY = 'e8067b53';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
  @ViewChild('movieSearchInput', { static: true })
  movieSearchInput!: ElementRef;
  apiResponse: any;
  isSearching: boolean;

  constructor(private httpClient: HttpClient) {
    this.isSearching = false;
    this.apiResponse = [];
  }

  ngOnInit() {
    fromEvent(this.movieSearchInput.nativeElement, 'keyup')
      .pipe(
        map((event: any) => event.target.value),
        filter((res) => res.length > 2),
        debounceTime(1000),
        distinctUntilChanged(),
        switchMap((term: string) => {
          this.isSearching = true;
          return this.searchGetCall(term);
        })
      )
      .subscribe({
        next: (res) => {
          this.isSearching = false;
          this.apiResponse = res;
        },
        error: (err) => {
          this.isSearching = false;
          console.error('error', err);
        },
      });
  }

  searchGetCall(term: string) {
    if (term === '') {
      return of([]);
    }

    let params = new HttpParams().set('s', term).set('apikey', APIKEY);

    return this.httpClient.get('http://www.omdbapi.com/', { params }).pipe(
      catchError((error) => {
        console.error('Error:', error);
        return throwError(error);
      })
    );
  }
}

We added the KeyUp callback event handler on input control and optimize its behaviour by adding some of the important RxJs functions like:

filter: The filter operator is used to selectively emit items from an Observable based on a condition.

In our case, filter((res) => res.length > 2) is used to only emit the event when the length of the input string is greater than 2. This is useful to limit the unessasary API calls until the min character limit is typed by user.

 

debounceTime: The debounceTime operator delays values emitted by the source Observable, but drops previous pending delayed emissions if a new value arrives on the source Observable. It is used to prevent a function from being called again until a certain amount of time has passed without it being called.

ThedebounceTime(1000) in our case is used to wait for 1 second (1000 milliseconds) of silence and waits for further user input then it emits the latest value. The debounceTime() helps to limit the number of API calls. Instead of making an API call for every key press, it waits until the user has stopped typing.

 

distinctUntilChanged: The distinctUntilChanged operator only emits when the current value is different than the last.

It is helping to prevent duplicate API calls. If the user types a character and then deletes it (the input string is the same as before), no API call will be made.

 

switchMap: The switchMap operator maps each value to an Observable, then it subscribes and begins emitting the values from that. If a new source item arrives, switchMap will stop emitting values from the previous source and immediately begin emitting values from the new source.

The switchMap is used to cancel the previous HTTP request made by searchGetCall if a new keyup event occurs. Helping to discard the previous search and start a new one every time the user types a new character and prevents not used API calls and reduces server load.

 

How to add a Condition in debouncing using the filter operator?

filter operator returns a boolean true or false. If we return a false debounce will not work. So we can easily add conditional debounce like if we want to check if any other value is there before making a hit, then we can do like this.

...
...
        filter(res => {
          if(res && this.user.corpid){
            return true;
          }else{
              return false;
          }
        }),
...
Also, check:

https://www.freakyjolly.com/angular-add-debounce-time-using-rxjs-6-to-optimize-search-input-for-api-results-from-server/

https://www.freakyjolly.com/angular-how-to-make-rest-search-call-using-rxjs-debounce/

So in the above example app, we learn how we can implement Debounce Time to optimize search functionality in an Angular application using RxJS Operators.

 

See a working demo here

 

Conclusion

We have completed the implementation of debounceTime and other useful RxJS functions to optimise server-side search in Angular application. Lets have a quick look what we have developed in our application above:

Debouncing: The debounceTime operator helps in limiting the number of API calls we make from application to the server. Insteal of making HTTP calls on every keyup event, we wait until the user has stopped typing for a certain amount of time for example 1000 ms. Only after that the API call is triggered. This significantly reduces the load on our server and improves the responsiveness of our application.

 

Filtering: We used RxJS filter operator to only make requests when the user has typed in at least a few characters. This prevents unnecessary requests to the server and helps to fetch meaningfully results.

 

Preventing Duplicates: We used the distinctUntilChanged operator to keep watch on queries made by the user. If the previous query remains same after user deleted the search term, then no new API call is triggered.

 

Cancelling Previous Requests: The switchMap operator allows us to cancel any ongoing HTTP request if a new request is made. For example a person types in a search term, but before its completion a new query is typed before previous HTTP class is completed. In this case the previous on going calls will get cancelled and a new HTTP call is made.

This particular behaviour ensures only the relevant results are fetched by keeping check on the quota limit on API calls for third party services we use.

 

By combining all above useful and important RxJS functions, we implemented a robust and userfrieldny search feature in Angular application by using a OMDB movie Search API service. Not only the user experience but also the API efficient is managed beautifully by keeping then under check and optimises to reduce server loads.

8 thoughts on “Angular 15 DebounceTime Search with HTTP API Results Example by Tutorial”

  1. I keep getting “Property ‘movieSearchInput’ has no initializer and is not definitely assigned in the constructor.” and ” Property ‘movieSearchInput’ is used before being assigned.” when running this. Is there a way to fix this? Thanks!

Leave a Comment

Your email address will not be published. Required fields are marked *