Interceptors can help provide a great user experience in your Angular app for HTTP requests. Let’s get started!
We will play with one of the powerful Angular features—interceptors—which help us simplify how we work with HTTP requests and responses.
It helps to put in one single place the control for all API calls; today, we’re going to see when they are helpful and how to create them and maximize their power.
If you have worked before with a service in Angular, interceptors will sound familiar because they are an Angular service with HttpInterceptor interface implementation.
The interceptors work between our app and the server and interact with the request and response.
The power of the interceptors comes from how they simplify all requests in our app instead of making changes in every place where we make HTTP calls.
Interceptors help us ensure to process all HTTP requests and responses before sending or getting the request, giving us the power to manage communication.
We have several places or scenarios to use them:
Next, we start to use the interceptors—let’s do it!
We will show a list of NBA players and teams making two HTTP requests, display the loading, and hide it when the server responds.
One solution shows the loading screen in each method for HTTP calls. What happens if we need to add more requests in the future with the same behavior? For that reason, using an interceptor is the best approach.
We will create two services in the application, the nba.service.ts
and loader.service.ts
.
The NbaService has getPlayers
and getTeams
methods to communicate with the API and return data.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map, Observable } from 'rxjs';
@Injectable({ provided: 'root' })
export class NbAService {
constructor(private httpClient: HttpClient) {
}
getPlayers(): Observable<any> {
return this.httpClient.get('https://www.balldontlie.io/api/v1/players').pipe(
map((response: any) => {
return response.data;
})
);
}
getTeams(): Observable<any> {
return this.httpClient.get('https://www.balldontlie.io/api/v1/teams').pipe(
map((response: any) => {
return response.data;
})
);
}
}
The loader service provides the property isLoading$
observable to get the status of the loader, and the methods show
and hide
to change his state.
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class LoaderService {
isLoading$ = new Subject<boolean>();
show(): void {
this.isLoading$.next(true);
}
hide(): void {
this.isLoading$.next(false);
}
}
Perfect, we have the services for the app. The next step is to create the Interceptor.
Feel free to read more about:
Interceptors are very similar to services using the @Injectable()
decorator, but instead implement the interface HttpInterceptor
. We must implement the intercept method with two parameters: req
to take the request
and next
to move to the next handler.
Create a new class with the @Injectable
decorator, implement the HttpInterceptor
interface and inject the loader service into the constructor.
In the intercept method, we call loader.show
to set the isLoading
property to true. The parameter next
is an observable httpRequest, so using the finalize operator from rxjs when it completes, call the hide method.
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpHandler, HttpRequest } from '@angular/common/http';
import { finalize, Observable } from 'rxjs';
import { LoaderService } from '../services/loader.service';
@Injectable()
export class LoaderInterceptor implements HttpInterceptor {
constructor(private loader: LoaderService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.loader.show();
return next.handle(req).pipe(
finalize(() => {
this.loader.hide();
}));
}
}
The HTTP request goes back from the server to the client, the Interceptor sets the loading property to true, and when it completes, it sets to false. Later we use the isLoading
property in the component to hide or show the loading screen.
Navigate to your app.module.ts and import the HttpClient module for the HTTP request in the imports area.
Add the Interceptor in the provider’s section and import the HTTP_INTERCEPTORS with the option useClass
to assign the LoaderInterceptor.
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { LoaderInterceptor } from './interceptors/loader.interceptor';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule { }
To use the power of Interceptor and the services, we need to import it where needed and bring the services from it through the component’s constructor.
We add three properties—players
and team
to store the data from the API response, and the loading$
property to get the value from loaderService.
Next, we need to call the methods from the services using two buttons in the UI to call the methods loadPlayers and LoadTeams.
In the TypeScript file, the code looks like this:
import { Component } from '@angular/core';
import { LoaderService } from './services/loader.service';
import { NbAService } from './services/NBA.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
players: any[] = [];
teams: any[] = [];
loading$ = this.loader.isLoading$;
constructor(private nbaApi: NbAService, private loader: LoaderService) { }
loadPLayers() {
this.players = [];
this.nbaApi.getPlayers().subscribe((data) => {
this.players = data;
});
}
loadTeams() {
this.nbaApi.getTeams().subscribe((data) => {
this.teams = data;
});
}
}
Add the CSS style for the loading screen is is a div with CSS styles to take the full-width colors to look like loading.
.loading {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
font-weight: bold;
color: yellow;
text-align: center;
z-index: 1;
background-color: rgba(43, 39, 39, 0.616);
}
In the template app.component.html iterate the player and teams object using the ngFor directive. When the user clicks on Load Player or Load Teams, the loading message appears at the top of the screen.
<div class="items">
<ul>
<li *ngFor="let p of players">{{ p.first_name }} {{ p.last_name }}</li>
</ul>
<ul>
<li *ngFor="let t of teams">{{ t.city }} {{ t.conference }}</li>
</ul>
</div>
<div>
<button (click)="loadPLayers()" >Load Players</button>
<button (click)="loadTeams()">Load Teams</button>
</div>
<!-- subscription to loading observable >
<div class="loading" *ngIf="loading$ | async">
<h1>Please wait...</h1>
</div>
Learn more about ngFor and async pipe.
When the user clicks the buttons Get Players
or Get Teams
, the Interceptor detects the HTTP request and sets visible to true, and when the server returns, the data switch back to false.
Because the observable loading$
subscribes to the loadingService
, it changes every time the Subject emits a new value.
The final result looks like the image:
We learned how to create an Interceptor and use it to control all HTTP requests in a single place. The perfect place to handle your HTTP request, the Interceptor is available in many scenarios to provide a great experience to the users.
You can find a complete code example for this article and play with the sample app at the following links:
Thanks for your time, and I hope you use Interceptor in the future.
Dany Paredes is a Google Developer Expert on Angular and Progress Champion. He loves sharing content and writing articles about Angular, TypeScript and testing on his blog and on Twitter (@danywalls).