Interested in latest HTTP response? Use RxJS SwitchMap operator

Reading Time: 3 minutes

Loading

Introduction

This post wants to illustrate how to use switchMap operator to cancel previous HTTP requests and retrieve the latest Pokemon by an id. It is an important technique to preserve scarce network resource on a device such as mobile phone that has limited data plan. It also improves performance because the application does not have to wait for previous requests to return and display until the last request comes back with the final result.

let's go

Bootstrap AppComponent

// main.ts

import 'zone.js/dist/zone';
import { provideHttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { PokemonComponent } from './pokemon/pokemon/pokemon.component';

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [PokemonComponent],
  template: `
    <app-pokemon></app-pokemon>
  `,
})
export class AppComponent {
  name = 'Angular';
}

bootstrapApplication(AppComponent, {
  providers: [provideHttpClient()]
});

In main.ts, I bootstrapped AppComponent as the root element of the application. It is possible because AppComponent is a standalone component and Component decorator defines standalone: true option. In the imports array, I imported PokemonComponent (that also a standalone component) and it is responsible to display information of a Pokemon. The second argument of bootstrapApplication is an array of providers and provideHttpClient makes HttpClient available for injection in components.

Retrieve Pokemon by HTTP

const retrievePokemonFn = () => {
  const httpClient = inject(HttpClient);
  return (id: number) => httpClient.get<Pokemon>(`https://pokeapi.co/api/v2/pokemon/${id}`)
    .pipe(
      map((pokemon) => ({
        id: pokemon.id,
        name: pokemon.name,
        height: pokemon.height,
        weight: pokemon.weight,
        back_shiny: pokemon.sprites.back_shiny,
        front_shiny: pokemon.sprites.front_shiny,
      }))
    );
}

retrievePokemonFn is a high order function that returns a function that calls Pokemon endpoint to retrieve the data by id.

Use switchMap to make latest HTTP Request

const btnMinusTwo$ = this.createButtonClickObservable(this.btnMinusTwo, -2);
const btnMinusOne$ = this.createButtonClickObservable(this.btnMinusOne, -1);
const btnAddOne$ = this.createButtonClickObservable(this.btnAddOne, 1);
const btnAddTwo$ = this.createButtonClickObservable(this.btnAddTwo, 2);

createButtonClickObservable(ref: ElementRef<HTMLButtonElement>, value: number) {
    return fromEvent(ref.nativeElement, 'click').pipe(map(() => value));
  }
}

createButtonClickObservable creates an Observable that emits a number when button click occurs. When button text is +1 or +2, the Observables emit 1 or 2 respectively. When button text is -1 or -2, the Observables emit -1 or -2 respectively.

const btnPokemonId$ = merge(btnMinusTwo$, btnMinusOne$, btnAddOne$, btnAddTwo$)
  .pipe(
     scan((acc, value) => { 
          const potentialValue = acc + value;
          if (potentialValue >= 1 && potentialValue <= 100) {
            return potentialValue;
          } else if (potentialValue < 1) {
            return 1;
          }

          return 100;
     }, 1),
     startWith(1),
     shareReplay(1),
  );

btnPokemonId$ is an Observable that emits the Pokemon id

this.pokemon$ = btnPokemonId$.pipe(switchMap((id) => this.retrievePokemon(id)));

When btnPokemonId$ emits a Pokemon id, I use switchMap to cancel previous HTTP requests and make a new request to retrieve the Pokemon. The result is then assigned to this.pokemon$ that is an Observable of Pokemon.

Render Pokemon in inline template

<ng-container *ngIf="pokemon$ | async as pokemon">
    <div class="pokemon-container">
       <label>Id:
          <span>{{ pokemon.id }}</span>
       </label>
       <label>Name:
          <span>{{ pokemon.name }}</span>
       </label>
       <label>Height:
          <span>{{ pokemon.height }}</span>
       </label>
       <label>Weight:
          <span>{{ pokemon.weight }}</span>
       </label>
    </div>
    <div class="container">
       <img [src]="pokemon.front_shiny" />
       <img [src]="pokemon.back_shiny" />
    </div>
 </ng-container>

Inline template uses async pipe to resolve pokemon$ Observable and displays the details of the Pokemon.

This is it and I have added HTTP capability to call Pokemon API. Moreover, I leverage switchMap to end earlier requests and trigger a new one whenever new pokemon id is emitted.

This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.

Resources:

  1. Github Repo: https://github.com/railsstudent/ng-pokemon/tree/main/projects/pokemon-demo-3
  2. Stackblitz: https://stackblitz.com/edit/angular-6qkuog?file=src/pokemon/pokemon/pokemon.component.ts
  3. Youtube: https://youtu.be/Y-AUQEYj7qg
  4. PokeAPI: https://pokeapi.co/