Angular Material Display Spinner with Every HTTP Request
Angular Show Spinner with Every HTTP Request

Angular Material Display Spinner with Every HTTP Request

It is an essential requirement for any application to show some kind of a loading animation – Angular apps are no exception. We often make HTTP requests to communicate with a back-end API. In this post, we will implement an abstract solution to show an Angular Material spinner while an HTTP request is taking place using a custom HTTP interceptor.

Although this tutorial uses the material library, you can easily use any other loading indicator component with this solution.

Here is a list of steps we’re going to follow:

  • Create an HTTP Interceptor to detect when a request has started/ended.
  • Create a shared Angular service to inform the components about the status of the request.
  • Use the service in components to show and hide angular material spinner in the user interface.

Demonstration of Angular Material spinner component:

Angular Material Spinner

Create a New Project

The first step is to create a new Angular project and add Material library references.

If you already have a project, you can skip this step.

Otherwise, check out this guide to setup a new project with Angular Material components. Alternatively, check out the full source code on GitHub.

Create a Spinner Service

Create a new file called `SpinnerService` and paste the following code:

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

@Injectable({
  providedIn: 'root' // No need to add to providers array!
})
export class SpinnerService {

  private visible: boolean;
  listeners = [];

  constructor() {
    this.visible = false;
  }

  onVisibilityChange(fn) {
    this.listeners.push(fn);
  }

  set visibility(value: boolean) {
    this.visible = value;
    this.listeners.forEach((fn) => {
      fn(value);
    });
  }

}

This is a very simple service that is responsible for storing the visibility status of the loader. It also broadcasts the status change to all listeners. Listeners are components and classes that subscribe to the onVisibilityChange event of this service.

When the visibility value changes, all other listeners will be notified of this change. Through data binding in Angular, we can bind the visibility of the spinner component to the visibility value of this service.

Bind the Spinner to the Service

The ideal place for putting the spinner component is usually the layout component. For the purpose of this tutorial, I am simply placing the spinner in the app.component.html file.

<mat-spinner [diameter]="50" *ngIf="showSpinner"></mat-spinner>

<button mat-raised-button color="primary" (click)="doWork()">Do Work</button>

As you can see, the visibility of the spinner above is bound to the showSpinner variable.

I have added a button to make a simple HTTP request so that we can see the spinner during the request.

The component, app.component.ts subscribes to the onVisibilityChange event of the service to update the showSpinner variable. This ensures that the variable value is always in sync with the service variable.

import { Component, OnInit } from '@angular/core';
import { SpinnerService } from './spinner.service';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
  
  showSpinner: boolean;

  constructor(private spinnerService: SpinnerService,
    private httpClient: HttpClient) { }

  ngOnInit() {
    // Subscribe to visibility change
    this.spinnerService.onVisibilityChange((value) => {
      this.showSpinner = value;
    });
  }

  doWork() {
    // Make a get request
    this.httpClient.get<any>('http://dummy.restapiexample.com/api/v1/employees')
      .subscribe(
        success => {
          console.log('Done');
        },
        error => {
          console.error('Error');
        }
      );
  }
}

With the shared service and the component logic, all we need to do now is detect HTTP requests with an HTTP interceptor and update the visibility value.

Create a Custom HTTP Interceptor

Angular 4.3 introduced HttpInterceptor, which provides a way to intercept HTTP requests and responses to transform or handle them before executing them.

Angular HTTP Interceptor flow

We will use a custom HTTP interceptor to display the loader before making the request and hide it when the request is finalised.

Create a new file called http-interceptor.ts and paste the following code:

import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpResponse } from '@angular/common/http';
import { HttpRequest } from '@angular/common/http';
import { HttpHandler } from '@angular/common/http';
import { HttpEvent } from '@angular/common/http';
import { tap } from 'rxjs/operators';
import { SpinnerService } from './spinner.service';

@Injectable()
export class CustomHttpInterceptor implements HttpInterceptor {

  constructor(private spinnerService: SpinnerService) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    // Show loader
    this.spinnerService.visibility = true;

    return next.handle(req).pipe(tap((event: HttpEvent<any>) => {
      // if the event is for http response
      if (event instanceof HttpResponse) {
        // stop our loader here
        this.spinnerService.visibility = false;
      }
    }, (err: any) => {
      // if any error we stop our loader
      this.spinnerService.visibility = false;
    })); 
  }
}

It is apparent that the above class implements the intercept method of HttpInterceptor from @angular/common/http.

The implementation grabs the current request and subscribes to the response. There are so many things that we can do at the time we grab the request, such as injecting a token for the purpose of authentication.

In our case, we just want to show a loader before passing on the request and hide it when the response comes back.

Tell Angular to Use the Custom Interceptor

We must now provide the custom interceptor in the providers array of the app.module.ts so that Angular knows how to intercept requests.

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { CustomMaterialModule } from './custom-material/custom-material.module';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { CustomHttpInterceptor } from './http-interceptor';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    CustomMaterialModule,
    HttpClientModule
  ],
  providers: [{
    provide: HTTP_INTERCEPTORS,
    useClass: CustomHttpInterceptor,
    multi: true
  }],
  bootstrap: [AppComponent]
})
export class AppModule { }

Summary

In this post, we implemented an abstract solution to display an Angular Material spinner component during an HTTP request using a custom interceptor.

The full source code is available on GitHub.

Umut Esen

Umut is an enthusiastic software developer, latest web and mobile technology adapter and primary author of onthecode.

Leave a Reply

Close Menu