Display a loader on every HTTP request using Interceptor in Angular 7

Posted by

As you may already know, as of version 4.3, Angular introduced a new HttpClient. One of the main features of this client is interception – a way to declare interceptors, sitting between our application and the backend. If you are aware of the middleware concept, you can visualize them as a middleware function/s, executed every time an Http call is made.

Read more about interceptors here

I don’t know about you, but when I first heard about this new feature, an idea how to simplify the loading process pop up to my head. Wouldn’t it be great instead of have a loaderShow() and loaderHide() functions all over our application, to have them in one single place ?

Yes, I would say. Today, you will learn how to use the power of interceptors to simplify your loader.

So, here is my implementation plan:

  1. Create a brand new Angular project (version 7)
  2. Create a new component for the loader (will use angular material for spinner)
  3. Create a loader service, which will be used to share the loading state between our interceptor and the loader component
  4. Create the Interceptor. Will be used to change the loading state to true when an http call is triggered and roll it back to false on finalize
  5. Modify the loader component to use the loader service created in point 3
  6. Test our solution by calling an external API and see if it works as expected

Create a brand new Angular project

As you probably already know, creating a project using the @angular/cli is really easy.

 ng new angular-app

In the time of writing this(02/26/2019), I was asked two question by the cli:

? Would you like to add Angular routing? No

? Which stylesheet format would you like to use? CSS

For our example it’s not a big deal what you will choose. I have decided to go with no routing and stylesheet format – css.

Create a new loader component

Using the angular cli, we will create a new component.

ng g c components/shared/loader

This will create the component in a folder ‘shared’ inside the component one and include it in the app.module.ts

Due to the fact we will use angular material for spinner, let’s install it, using this command:

npm install --save @angular/material @angular/animations @angular/cdk

When the install is finished, you can import the spinner in the app.module.ts

import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';

And include it in the imports array(also there):

  imports: [
    BrowserModule,
    MatProgressSpinnerModule
  ]

An important part of the angular material setup is to also import the theme you want to use. Don’t skip this step, as it can cause a problems in future with your loader visibility.

I have decided to use the deeppurple-amber by importing it in styles.css (the global styles file)

@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';

Now we should be able to use the angular material spinner in our loader.component.html

<div class="overlay">
  <mat-progress-spinner class="spinner" [color]="color" [mode]="mode" [value]="value">
  </mat-progress-spinner>
</div>

And the following setup in loader.component.ts file:

import { Component } from '@angular/core';

@Component({
  selector: 'app-loader',
  templateUrl: './loader.component.html',
  styleUrls: ['./loader.component.css']
})

export class LoaderComponent {
  color = 'primary';
  mode = 'indeterminate';
  value = 50;
}

Instead of hard-coding the color, mode and value properties in the template, I have decided to declare them in our .ts file, which is a better practice and also more readable in my opinion.

Last but not least – loader.component.css

.overlay {
  position:fixed;
  display:block;
  width:100%;
  height:100%;
  top:0;
  left:0;
  background-color:rgba(74,74,74,.8);
  z-index:99999;
}

.spinner {
  position:absolute;
  top:50%;
  left:50%;
  transform: translate(-50%,-50%);
}

In short, it just resize the container with class .overlay(our loader component) to fill the whole screen and position the spinner in the center.

You can test it by importing the loader component selector in app.component.html

<app-loader></app-loader>
<div>Lorem ipsum! </div>

Start the application by:

ng serve

and check the result. You should see the purple spinner spinning 🙂

Create the loader service

The idea behind the loader service is to expose a Subject with name isVisible, to whom we can emit true/false  values from the interceptor and listen for changes from our loader component.

In case you are not familiar with what is Subject, you can check the official RxJS documentation. In short, it’s a special type of Observable, that can act both as Observable and Observer in the same time.

In src/app/services let’s create a service LoaderService(loader.service.ts) with the following code:

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable()
export class LoaderService {
    isLoading = new Subject<boolean>();
    show() {
        this.isLoading.next(true);
    }
    hide() {
        this.isLoading.next(false);
    }
}

Basically we are creating a new variable isLoading with type Subject that expect boolean data only and two methods – show and hide, that push data(in our case boolean flags) to the Subject.

And of course, let’s import it in our providers array in app.module.ts

import { LoaderService } from './services/loader.service';

….

providers: [LoaderService],


Creating the interceptor

In src/app/interceptors, let’s create a loader.interceptor.ts file with the following content:

import { Injectable } from "@angular/core";
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { Observable } from "rxjs";
import { finalize } from "rxjs/operators";

import { LoaderService } from '../services/loader.service';

@Injectable()
export class LoaderInterceptor implements HttpInterceptor {
    constructor(public loaderService: LoaderService) { }
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        this.loaderService.show();
        return next.handle(req).pipe(
            finalize(() => this.loaderService.hide())
        );
    }
}

This interceptor will change the subject value to true, when a request starts, “hide” it when the request is “finalized”.

You can check the RxJS documentation to learn more about finalize. The cool thing is that it call our callback function on both success and error responses. This way, we can be sure that our application will not end up with non-stop spinning loader.

Now, when our interceptor is ready, we just have to tell our angular application to use it. Like this:

providers: [
    LoaderService,
    { provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true }
]

And import the HTTP_INTERCEPTORS from @angular/common/http.

import { HTTP_INTERCEPTORS } from '@angular/common/http';

Modify the LoaderComponent to use the loaderService

As we don’t want to showing the spinner non stop, we will put the whole loader.component.html in *ngIf

<div *ngIf="isLoading | async" class="overlay">
  <mat-progress-spinner class="spinner" [color]="color" [mode]="mode" [value]="value">
  </mat-progress-spinner>
</div>

And use the loaderService to get the correct state:

import { Component } from '@angular/core';
import { Subject } from 'rxjs';

import { LoaderService } from '../../../services/loader.service';

@Component({
  selector: 'app-loader',
  templateUrl: './loader.component.html',
  styleUrls: ['./loader.component.css']
})
export class LoaderComponent {
  color = 'primary';
  mode = 'indeterminate';
  value = 50;
  isLoading: Subject<boolean> = this.loaderService.isLoading;

  constructor(private loaderService: LoaderService){}
}

We have to note that this can also be achieved with the traditional subscribe/unsubscribe approach, but I prefer to use the async pipe as it looks more cleaner and you shouldn’t care about memory leaks if you miss to unsubscribe.

Let’s test our solution now with a real API call 🙂

We will use reqres.in instead of build our own backed as it will be much faster.

https://reqres.in/api/users?page=2

Import the HttpClientModule in our app.module.ts and include it in the imports array.

Change the html app.component.html to the following:

<app-loader></app-loader>
<div>
    <button (click)="callApi()">Click</button>
</div>

And create a super simple logic in app.component.ts which will call the API on button click:

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-loader';

  constructor(private http: HttpClient) { }
  callApi() {
    this.http.get('https://reqres.in/api/users?page=2')
      .subscribe(data => {
        console.log(data);
      })
  }
}

Save the changes, open the application, click the button and voalah 🙂 You should be able to see the spinner.

I hope you enjoyed this tutorial.

You can see the full code here: https://github.com/vikobg/first-class-js/tree/master/angular-loader

And a working example here – https://firstclassjs.com/tutorials/angular-loader/

Good luck!