NashTech Blog

Effortlessly Handle API Calls on Non-Input Change in Angular Using RxJS switchMap & filter

Table of Contents
RXJS

One of the fairly common challenges, especially for an Angular developer, is getting a condition to call APIs on value change but not on every user interaction or even an empty input. A solution with RXJS operators like switchMap and filter makes it straightforward, maintaining clean and efficient code.

Let’s do it step by step, by way of example, which sets off an API call on the event of the dropdown change and filters the empty value.

The Problem: In a typical Angular form, you may want to call an API when the input value change. However, there are a couple of problems:

  • Do not make the API call for an empty or invalid value.
  • Make sure the most current value is taken, even if the user quickly hits multiple values.
  • Write clean and testable code to handle these efficiently.

Before starting, if you don’t know about RxJS switchMap Operator then follow this below link.

Unlocking the Power of switchMap and mergeMap in Angular: Real-Life Scenarios and Code Samples

If you want to learn about JavaScript Notification API click here .

Solution Overview: We are going to use the switchMap operator to handle the API calls. The filter operator will help us skip the empty values and by debounceTime operator will delay the call for some time.

Let’s Implement It Step by Step

HTML Template

<form [formGroup]="myForm">
 <mat-form-field class="example-full-width">
    <mat-label>Suto Search</mat-label>
    <input type="text"
           placeholder="Pick one"
           aria-label="text"
           matInput
           formControlName="myDropdown"
           [matAutocomplete]="auto">
    <mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
      @for (option of options; track option) {
        <mat-option [value]="option">{{option}}</mat-option>
      }
    </mat-autocomplete>
  </mat-form-field>
</form>

Component Logic

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { of } from 'rxjs';
import { filter, switchMap } from 'rxjs/operators';
import { ApiService } from './api.service';  // Assuming a service for API calls

@Component({
  selector: 'app-component',
  templateUrl: './app-component.component.html',
})
export class AppComponent implements OnInit {
  myForm: FormGroup;
  options = ['Option 1', 'Option 2', 'Option 3'];

  constructor(private fb: FormBuilder, private apiService: ApiService) {}

  ngOnInit() {
    this.myForm = this.fb.group({
      myDropdown: [''],
    });

    // Listening to dropdown value changes and handling API calls
    this.myForm.get('myDropdown')?.valueChanges
      .pipe(
        filter(value => value.trim() !== ''), 
        debounceTime(500),       
       switchMap(value => this.apiService.getData(value))  // Switch to latest API call
      )
      .subscribe(response => {
       this.options = res;
      });
  }
}

Let’s understand logic

  • filter(value=> value.trim() !=”): It will ensure that the API will not call when input is empty or only contains whitespace.
  • debounceTime(500): It will add 500 ms delay after the last key press by the user to avoid frequently calling the API.
  • swicthMap(value => this.apiService.getData(value)): It will cancel all previous API requests and initiate a new one for each valid input change.

Now the above logic will insure that our problem statement will be solved for development purposes.

Our next task is writing a test case for the same to make sure that we will maintain code quality and test our code.

Test Case for This Implementation

Spec File Changes


import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { ApiService } from 'api.service';
import { of, throwError } from 'rxjs';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import {MatAutocompleteModule} from '@angular/material/autocomplete';

describe('AppComponent', () => {
  let component: AppComponent;
  let fixture: ComponentFixture<AppComponent>;
  let apiService: ApiService;

  beforeEach((() => {
    TestBed.configureTestingModule({
      declarations: [ CustomAutoSuggestionTextFieldComponent ],
      imports: [ ReactiveFormsModule, HttpClientTestingModule,
        NoopAnimationsModule, MatFormFieldModule, MatAutocompleteModule

      ],
    })
    .compileComponents();
    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
    apiService = TestBed.inject(ApiService);
        component.myForm = new FormGroup({
        myDropdown: new FormControl('')
     });

  }));

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should call API and console log options and searching to false on input value change', fakeAsync(() =>
    spyOn(apiService, "getData").and.returnValue(of(['suggestion1', 'suggestion2']));
    component.ngOnInit();
    component.myForm.get('myDropdown').setValue('suggestion');
    tick();
    tick(500);
    expect(component.options).toEqual(['suggestion1', 'suggestion2']);
  }));
});

Angular provides helper functions fakeAsync and tick to handle asynchronous tests and you can also verify that we are using these helper function for asynchronous opeartion.

Key Points

  • filter: This operator ensures that no API call is made when the value is empty.
  • switchMap: It cancels previous API calls when a new value is selected, ensuring only the latest one is processed.
  • Testability: The test cases show how to verify that the API is only triggered for valid values, making the code reliable.

Final Thoughts

Using RxJS operators like switchMap and filter keeps your Angular app responsive and clean, avoiding redundant API calls and simplifying code logic. By handling the non-input changes efficiently, you ensure smooth user experiences and improve app performance.

For more such blogs, follow me on LinkedIn and Medium.

Leave a Comment

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

Suggested Article

Scroll to Top