How to add page loader globally every component in Angular
First, we need a service for holding our loading state;
ng g s loader
After that, we adding getter and setter for loading property;
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class LoaderService {
private loading: boolean = false;
constructor() { }
setLoading(loading: boolean) { this.loading = loading; }
getLoading(): boolean { return this.loading; }
}
After creating service, now we should have a loading component for showing our loading gif, animation or image whatever you want;
ng g c loader
The code for loader component is below;
import { Component } from '@angular/core';
import { LoaderService } from '../loader.service';
@Component({
selector: 'app-loader',
templateUrl: './loader.component.html',
styleUrls: ['./loader.component.css']
})
export class LoaderComponent {
constructor(public loader: LoaderService) { }
}
Our loader.component.html content;
<div *ngIf="this.loader.getLoading()" class="loader-container">
<span class="loader"></span>
</div>
loader.component.css file: (I got this css loader animation from https://cssloaders.github.io and you can find many more options, thanks for great animations)
/* our container class for full screen container */
.loader-container{
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 100%;
z-index: 9999;
background-color: rgb(38 48 56 / 90%);
}
/* spinner */
.loader {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
max-width: 6rem;
margin-top: 3rem;
margin-bottom: 3rem;
}
.loader:before,
.loader:after {
content: "";
position: absolute;
border-radius: 50%;
animation: pulsOut 1.8s ease-in-out infinite;
filter: drop-shadow(0 0 1rem rgba(255, 255, 255, 0.75));
}
.loader:before {
width: 100%;
padding-bottom: 100%;
box-shadow: inset 0 0 0 1rem #fff;
animation-name: pulsIn;
}
.loader:after {
width: calc(100% - 2rem);
padding-bottom: calc(100% - 2rem);
box-shadow: 0 0 0 0 #fff;
}
@keyframes pulsIn {
0% {
box-shadow: inset 0 0 0 1rem #fff;
opacity: 1;
}
50%,
100% {
box-shadow: inset 0 0 0 0 #fff;
opacity: 0;
}
}
@keyframes pulsOut {
0%,
50% {
box-shadow: 0 0 0 0 #fff;
opacity: 0;
}
100% {
box-shadow: 0 0 0 1rem #fff;
opacity: 1;
}
}
Now we should put our component to the app.component.html file
<app-spinner></app-spinner>
Now we are ready for the setting our loading state. We will using interceptor to doing this. Lets generate our interceptor;
ng g interceptor loading
Now we need to set the loading status according to all requests. After request completed, we should set back to false for hide the our loader container.
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { LoaderService } from '../services/loader.service';
@Injectable()
export class LoadingInterceptor implements HttpInterceptor {
private totalRequests = 0;
constructor(private loaderService: LoaderService) {}
intercept(
request: HttpRequest<unknown>,
next: HttpHandler
): Observable<HttpEvent<unknown>> {
this.totalRequests++;
this.loaderService.setLoading(true);
return next.handle(request).pipe(
finalize(() => {
this.totalRequests--;
if (this.totalRequests == 0) {
this.loaderService.setLoading(false);
}
})
);
}
}
After that, we should add our interceptor to providers in app.module.ts file:
import { LoadingInterceptor } from "./loading.interceptor";
@NgModule({
providers: [{ provide: HTTP_INTERCEPTORS, useClass: LoadingInterceptor, multi: true }],
})
export class AppModule {}
Now we are done!
Additional information:
If we want to exclude some of our request from loading state we adding some line of codes in loading.interceptor.ts;
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { LoaderService } from '../services/loader.service';
@Injectable()
export class LoadingInterceptor implements HttpInterceptor {
private totalRequests = 0;
constructor(private loaderService: LoaderService) {}
intercept(
request: HttpRequest<unknown>,
next: HttpHandler
): Observable<HttpEvent<unknown>> {
this.totalRequests++;
// If our request endpoint not contains this, loading state will be true
// You can change this for list of endpoints or something like whatever you want
if(!request.url.includes('/api/endpoint')){
this.loadingService.setLoading(true);
}
return next.handle(request).pipe(
finalize(() => {
this.totalRequests--;
if (this.totalRequests == 0) {
this.loaderService.setLoading(false);
}
})
);
}
}