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.
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.