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