In this post, we will learn how to use Angular’s Interceptor class to show a common loader/ spinner indicating about an API HTTP call is in progress. We’ll create a UI component with a custom loader to keep track of every HTTP call going from the client application to a remote server.
Angular Interceptors can be used in a number of ways as they work pretty well in manipulating and managing HTTP calls to communicate that we make from a client-side web application. Single Page Applications(SPA) build using Angular framework, use HttpClient module to make HTTP calls to the server for retrieving data to update pages with dynamic information. So it becomes very important to provide information about the status of API calls we make to a remote server to the users. Loaders/ Spinners and sometimes Progress bars are shown to inform the user that there are some server requests going on.
Using Angular interceptors we can also handle responses or errors returned by calls. We will maintain the stack or queue to calls and will show the loader if the requests queue is 1 or more than 1 as sometimes more than one call is also made to get data in dynamic applications.
Also Check: Angular Spinner Loader using ng-http-loader package
Our demo Angular project will have a custom MyLoaderComponent
component, loader Interceptor and a loader service to show/ hide our loader using RxJs BehaviorSubject
observable.
Let’s have a look at the steps we’ll perform during this implementation.
How to add Custom HTTP Loader using Angular Interceptors?
Step 1 – Update the Angular CLI tool to the latest version
Step 2 – Create a new Angular project
Step 3 – Create a loader service to broadcast isLoading
boolean using the RxJs BehaviorSubject
observable.
Step 4 – Create a Loader Interceptor to keep HTTP calls stack and handle errors as well.
Step 5 – Create a custom MyLoaderComponent
Step 6 – Add a custom loader component in the app component template.
Step 7 – Update App Module with HttpClientModule
, Interceptor and Loader service.
Step 8 – Run Application
Let’s get started!
Step 1 – Update Angular CLI tool
Angular Interceptors are available from versions greater than 4.3 so you can go ahead with any version above that. But it is always preferred to upgrade the Angular CLI tool to the latest version.
Run the following npm command in the terminal window to upgrade to the current version 13.3.4
npm install -g @angular/cli
Step 2 – Create a new Angular project
Using the Angular CLI tool, create a new Angular 9 project by running below ng command
ng new angular-interceptor-loader
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS
Move to the project root directory
$ cd angular-interceptor-loader
Open the project by hitting the below shortcut command in the Visual Studio Code(if you have installed it)
$ code .
Step 3 – Create a Loader Service
Now we will create a loader service, which will have RxJs <strong>BehaviorSubject</strong>
observable. We will subscribe to this special Observable type to keep a close watch on the HTTP request queue which will be emitted from an Interceptor which we will create in the next step.
The BehaviorSubject
always emit the last value directly. You can get more details on this article by Luuk
Run the following ng command in CLI to create a loader service in the app
folder in the project.
ng generate service services/loader --skipTests=true
The --skipTests=true
option is used to skip spec files used for testing.
After creating the service replace ~services/loader.service.ts with the following code.
//loader.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class LoaderService {
public isLoading = new BehaviorSubject(false);
constructor() { }
}
Step 4 – Create Loader Interceptor
In Angular Interceptor, we’ll create an array requests
of type HttpRequests
. Whenever a new Http call hits, it will be pushed in it. On successfully completing the Http call, that request will be popped out by calling the removeRequest()
.
Create the LoaderInterceptor
in the interceptors folder by running below generate command
ng generate service interceptors/loader-interceptor --skipTests=true
The LoaderInterceptor
class implements HttpInterceptor
to override its intercept
method.
Now replace following code in ~interceptors/loader-interceptor.service.ts
// loader-interceptor.service.ts
import { Injectable } from '@angular/core';
import {
HttpResponse,
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { LoaderService } from '../services/loader.service';
@Injectable()
export class LoaderInterceptor implements HttpInterceptor {
private requests: HttpRequest<any>[] = [];
constructor(private loaderService: LoaderService) { }
removeRequest(req: HttpRequest<any>) {
const i = this.requests.indexOf(req);
if (i >= 0) {
this.requests.splice(i, 1);
}
this.loaderService.isLoading.next(this.requests.length > 0);
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.requests.push(req);
console.log("No of requests--->" + this.requests.length);
this.loaderService.isLoading.next(true);
return Observable.create(observer => {
const subscription = next.handle(req)
.subscribe(
event => {
if (event instanceof HttpResponse) {
this.removeRequest(req);
observer.next(event);
}
},
err => {
alert('error' + err);
this.removeRequest(req);
observer.error(err);
},
() => {
this.removeRequest(req);
observer.complete();
});
// remove request from queue when cancelled
return () => {
this.removeRequest(req);
subscription.unsubscribe();
};
});
}
}
The intercept
method is required in HttpInterceptor implemented class, this will help in modifying responses and requests made through HTTP Client in Angular application.
The removeRequest
method will keep track of the number of HTTP calls in progress by maintaining the queue in an array.
We are emitting true
/false
boolean value to LoaderService using the next method checking the length of pending requests
.
Step 5 – Create a custom Loader Component
Now, we’ll create a custom styled loader spinner component with its own style to keep loader separate and more customizable. Run below generate
command to create MyLoaderComponent
in the components folder.
ng generate component components/my-loader --skipTests=true
Place below HTML template in ~components/my-loader/my-loader.component.html on new loader component.
<!-- my-loader.component.html -->
<div class="progress-loader" [hidden]="!loading">
<div class="loading-spinner">
<img src="https://loading.io/mod/spinner/gear-set/index.svg">
<span class="loading-message">Please wait...</span>
</div>
</div>
Check more loader images here.
Add loader style in the ~components/my-loader/my-loader.component.css
/* my-loader.component.css */
.loading-spinner{
background-color: #0000001f;
position: absolute;
width: 100%;
top: 0px;
left: 0px;
height: 100vh;
align-items: center;
justify-content: center;
display: grid;
}
.loading-spinner img{
align-self: end;
}
.loading-message{
text-align: center;
align-self: start;
}
Replace the ~components/my-loader/my-loader.component.ts file with the following code
// my-loader.component.ts
import { Component, OnInit } from '@angular/core';
import { LoaderService } from '../../services/loader.service';
@Component({
selector: 'app-my-loader',
templateUrl: './my-loader.component.html',
styleUrls: ['./my-loader.component.css']
})
export class MyLoaderComponent implements OnInit {
loading: boolean;
constructor(private loaderService: LoaderService) {
this.loaderService.isLoading.subscribe((v) => {
console.log(v);
this.loading = v;
});
}
ngOnInit() {
}
}
In the component class above, we are subscribing to the BehaviorSubject
which we defined in the service. Based on that the loader
boolean will show/hide loader based on a flag returned.
Step 6 – Add Loader Component
Just place the app-my-loader
loader component in the main parent application component, which is App component in our case. Add the loader component in the app.component.html file, which is the root component template as shown below.
...
<button (click)="makeHttpCall()">Make Http Call</button>
<app-my-loader></app-my-loader>
Add a method makeHttpCall()
to test an Http get()
call in the app.component.ts file as shown below:
// app.component.ts
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
title = 'angular-interceptor-loader';
constructor(public http: HttpClient) {}
makeHttpCall() {
this.http
.get('https://jsonplaceholder.typicode.com/comments')
.subscribe((r) => {
console.log(r);
});
}
}
Step 7 – Update App Module.
Finally, we need to import the LoaderService
, LoaderInterceptor
and HttpClientModule
to make Http calls in the app.module.ts file.
Also, we need to import HTTP_INTERCEPTORS
from @angular/common/http
to enable Interceptors in the application.
// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { LoaderService } from './services/loader.service';
import { LoaderInterceptor } from './interceptors/loader-interceptor.service';
import { MyLoaderComponent } from './components/my-loader/my-loader.component';
@NgModule({
declarations: [AppComponent, MyLoaderComponent],
imports: [BrowserModule, HttpClientModule],
providers: [
LoaderService,
{ provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true },
],
bootstrap: [AppComponent],
})
export class AppModule {}
Pheww.. we are finally done 🙂 a bit long way by only one-time implementation adds a lot for a good user experience.
Step 8 – Run Application
Now you can check your application by running $ ng serve --open
in the terminal. In this way, we can show a common loader in Angular based applications using the power of Interceptors.
Check next post to see how to add Spinners and Progress bar using Angular Material.
Leave a Reply