Angular 9|8|7 | Show Global Spinner/ Loader on HTTP calls in few steps using Angular Interceptors in Angular 4.3+

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 trace 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 loader if requests queue is 1 or more then 1 as sometimes more then 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.

1) Update Angular CLI tool to the latest version 9.1.3

2) Create a new Angular project

3) Create a loader service to broadcast isLoading boolean using the RxJs BehaviorSubject observable.

4) Create a Loader Interceptor to keep HTTP calls stack and handle errors as well.

5) Create a custom MyLoaderComponent

6) Add custom loader component in the app component template.

7) Update App Module with HttpClientModule, Interceptor and Loader service.

 

 

Let’s get started!

#1 Update Angular CLI tool

Angular Interceptors are available from versions greater then 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 following npm command in the terminal window to upgrade to current version 9.1.3

$ npm install -g @angular/cli

 

#2 Create a new Angular project

Using 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? CSS

Move to the project root directory

$ cd angular-interceptor-loader

Open the project by hitting below shortcut command in the Visual Studio Code(if you have installed)

$ code .

 

#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 HTTP request queue which will be emitted from an Interceptor which we will create in next step.

The BehaviorSubject always emit last value directly. You can get more details on this article by Luuk

Run 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 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() { }
}

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

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

 

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

<router-outlet></router-outlet>

<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);
      });
  }
}

 

#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 { AppRoutingModule } from './app-routing.module';
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,
    AppRoutingModule,

    HttpClientModule
  ],
  providers: [
    LoaderService,
    { provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

https://www.freakyjolly.com/angular-http-spinner-loader-using-ng-http-loader-tutorial-by-example/

https://www.freakyjolly.com/angular-global-loader-spinner-on-http-calls-interceptors-tutorial/

 

Pheww.. we are finally done 🙂 a bit long way by only one-time implementation adds a lot for a good user experience.

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.

19 thoughts on “Angular 9|8|7 | Show Global Spinner/ Loader on HTTP calls in few steps using Angular Interceptors in Angular 4.3+”

  1. by the way, that is easy to fix: add this on top of the interceptor before the ‘this.request.push(req)’ line :
    if (req.reportProgress) {return next.handle(req);}

  2. Nice one. Unfortunately your interceptor does not pass through the event of type: 1 (HttpEventType.UploadProgress).
    It took me quite some time to figure out why my upload progress did not work…..
    

  3. Very helpful and well written post.
    But can we eliminate LoaderSevice and directly use LoaderInterceptor inside MyLoaderComponent? I tried by adding this

    public isLoading = new BehaviorSubject(false)
    

    in LoaderInterceptor and inside intercept method replaced this.loaderService.isLoading.next(true) with this.isLoading.next(true) but not sure why it is not working.
    Any help would be appreciated.
    Thanks

  4. Kevork Yerevanian

    I got a warning that in the code
    return Observable.create((observer)
    It says that “create” is deprecated and it’s recommended to use “new Observer”. Do you have such a version of your code? It would be nice. In any case, your code as it is, works perfectly. Thanks again!

    1. Buddy you can try this ( worked for me )

      // 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 ../shared/components/loader/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);
      }

      if (this.requests.length > 0) {
      this.loaderService.show();
      } else {
      this.loaderService.hide();
      }
      }

      intercept(
      req: HttpRequest<any>,
      next: HttpHandler,
      ): Observable<HttpEvent<any>> {
      this.requests.push(req);

      console.log(No of requests—> + this.requests.length);

      this.loaderService.show();
      return new Observable(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();
      };
      });
      }
      }

Leave a Reply to Asha Cancel Reply

Your email address will not be published. Required fields are marked *