How to implement a global loader with Angular Material
How do you create a global loading spinner with Angular Material?
When it comes to implementing a loading spinner for your Angular application there are different ways to do this.
You could subscribe to the Angular router events.
You could also use an HTTP interceptor.
You can use a spinner inside your components - for example - or use a loading spinner on the Angular material buttons.
Or you could create a global spinner.
Whatever the case, here's the quickest way to add an Angular loading spinner to an Angular application.
Today, you're going to learn an easy way to set up a global spinning loader in Angular with Angular Material. This loading spinner that will be shown while your app is sending and receiving data from an API server.
1. Create a loading service
We'll be using the Angular CLI a lot so get your terminal (or command prompt) ready.
Here's the first command we'll use to generate our loading service. This service will be used as a mini state-manager for our application.
It would be better to use a state management library to manage the loading state of our application. But since I promised to make this fast, here's how to leap-frog the sticky work of getting a state manager set up.
ng generate service loading
Here's the code for our new service.
import { Injectable } from '@angular/core';
import { BehaviorSubject, delay, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class LoadingService {
loading = new BehaviorSubject<boolean>(false);
constructor() { }
setLoading(loading: boolean) {
this.loading.next(loading);
}
}
2. Create the spinner component
This is the component that will create the overlay effect and display a loading spinner in the middle of the page.
ng generate component spinner
Here's the code for our component's Typescript file.
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { LoadingService } from '../loading.service';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
@Component({
selector: 'app-spinner',
standalone: true,
imports: [CommonModule, MatProgressSpinnerModule],
templateUrl: './spinner.component.html',
styleUrl: './spinner.component.scss'
})
export class SpinnerComponent {
constructor(public loader: LoadingService) {}
}
As well as the code for our component's HTML file.
<div *ngIf="this.loader.loading | async" class="spinner-container">
<div class="row">
<mat-spinner></mat-spinner>
</div>
</div>
And last of all, the CSS styles and animations for the loading spinner.
.spinner-container {
height: 100%;
padding: 0;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
}
.row {
width: auto;
}
3. Set up our global spinner component and API call
We will also need to add the new spinner component to the app.component.html
file.
And also set up our simple API call.
Here's the Typescript code for our app.component.ts
component.
import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { SpinnerComponent } from './spinner/spinner.component';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, SpinnerComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent implements OnInit {
data: any;
constructor(private httpClient: HttpClient) { }
ngOnInit(): void {
this.httpClient.get("https://dummyjson.com/todos")
.subscribe((response: any) => {
this.data = response.todos;
console.log(this.data);
})
}
}
And the code for our app.component.html
.
<app-spinner></app-spinner>
<table>
<tr>
<th>Id</th>
<th>Todo</th>
</tr>
@for (item of data; track $index) {
<tr>
<td>{{ data[$index].id }}</td>
<td>{{ data[$index].todo }}</td>
</tr>
}
</table>
4. Create an HTTP Interceptor
Our HTTP interceptor will be used to set the state of our loading spinner.
We'll begin by generating it with the Angular CLI.
ng generate interceptor app
Then, we'll modify our HTTP interceptor to spy on all outgoing requests and flip the state to loading. As soon as all outgoing requests have completed, it will flip the loading state back to false.
Here's the code.
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { LoadingService } from './loading.service';
import { finalize } from 'rxjs';
let totalRequests = 0;
export const appInterceptor: HttpInterceptorFn = (req, next) => {
const loadingService = inject(LoadingService);
totalRequests++;
loadingService.setLoading(true);
return next(req).pipe(
finalize(() => {
totalRequests--;
if (totalRequests == 0) {
loadingService.setLoading(false);
}
})
);
};
All that's left to do now is to register our HTTP interceptor inside the app.config.ts
file.
We'll use the withInterceptors
function like this.
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { appInterceptor } from './app.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([appInterceptor]))
]
};
We're done!
The Final Result 👏🏻 👏🏻
And here's a demo of the result.
What do you think of this global Material loading spinner? Is there any way it could be improved?
Or maybe you know of an easier or quicker way?
Don't hesitate to let me know what you think.
Angular Developer & Consultant
P.S. - Need an Angular expert to audit your code base? I've got a few slots open.