Ever faced this?
- You make two or more HTTP requests in Angular
- Multiple loaders show up — and flicker
- Or the loader hides before all calls are done
Let’s fix that! In this article, I’ll walk you through how to build a global, smart HTTP loader using Angular interceptors. It’ll:
✅ Show just one loader for any number of API calls
✅ Stay visible until all requests finish
✅ Make your app feel polished and professional
🧠 What’s the Problem?
Angular apps often make multiple HTTP calls — maybe to load user data, dashboard stats, and notifications.
But if each call has its own loader logic, the UI becomes a mess:
- Loaders flash on and off
- Users think the app is done loading when it's not
- Code becomes hard to manage
Let’s centralize this using HTTP interceptors and a shared LoaderService.
🔧 Step-by-Step Implementation
1️⃣ Create the LoaderService
This service will track the number of active requests.
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class LoaderService {
private requestCount = 0;
public isLoading$ = new BehaviorSubject<boolean>(false);
show() {
this.requestCount++;
if (this.requestCount === 1) {
this.isLoading$.next(true);
}
}
hide() {
if (this.requestCount > 0) {
this.requestCount--;
}
if (this.requestCount === 0) {
this.isLoading$.next(false);
}
}
reset() {
this.requestCount = 0;
this.isLoading$.next(false);
}
}
2️⃣ Create the HTTP Interceptor
This interceptor will automatically call show() and hide().
// loader.interceptor.ts
import { Injectable } from '@angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { LoaderService } from './loader.service';
@Injectable()
export class LoaderInterceptor implements HttpInterceptor {
constructor(private loaderService: LoaderService) {}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
this.loaderService.show();
return next.handle(req).pipe(
finalize(() => this.loaderService.hide())
);
}
}
💡 Key point: The finalize() operator ensures hide() is called whether the request succeeds or fails.
3️⃣ Register the Interceptor
Add it to your app module:
// app.module.ts
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { LoaderInterceptor } from './loader.interceptor';
@NgModule({
// ...
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: LoaderInterceptor,
multi: true,
},
],
})
export class AppModule {}
4️⃣ Show the Loader in the UI
You can now show a spinner component anywhere using the isLoading$ observable.
Here’s a simple example using Angular Material:
<!-- app.component.html -->
<mat-progress-bar
*ngIf="isLoading$ | async"
mode="indeterminate"
color="primary"
></mat-progress-bar>
<router-outlet></router-outlet>
// app.component.ts
import { Component } from '@angular/core';
import { LoaderService } from './loader.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
isLoading$ = this.loaderService.isLoading$;
constructor(private loaderService: LoaderService) {}
}
✅ Output Demo
When multiple requests fire:
- You’ll see one smooth loader
- It will disappear only when all requests complete
- Works across any part of your app
🛠 Tips and Enhancements
- Delay loader show by 100ms to avoid flashing for fast calls
- Add an exclude list inside the interceptor to skip loader for certain URLs (e.g. logging, analytics)
- Debounce or throttle UI updates if you’re tracking many rapid requests
🧪 Bonus: ngx-loading-bar (Optional Lib)
Prefer a library? ngx-loading-bar integrates with Angular interceptors out of the box.
npm install @ngx-loading-bar/core @ngx-loading-bar/http-client
// app.module.ts
import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client';
@NgModule({
imports: [LoadingBarHttpClientModule],
})
export class AppModule {}
Drop this in your layout:
<ngx-loading-bar></ngx-loading-bar>
Boom 💥 Instant loader for all HTTP requests.
🏁 Conclusion
You now have a professional, smart loader system that just works 🔁
It’s:
- Centralized
- Lightweight
- Easy to maintain
No more copy-pasting loader logic all over your components!
👋 About Me
I’m a frontend developer working with Angular, React, and AI-assisted tools. I love writing clean, scalable code and sharing what I learn along the way.
Let’s connect in the comments — and don’t forget to ❤️ the post if you found it useful!
Would you like me to write the next article in this Angular Pro Tips series on dynamic dashboards with charts?
Top comments (1)
Hi @sulimanmunawarkhan , I just wrote an article about interceptors and I think it complements yours (even though mine is for Angular 20)
⚡ Conditional Interceptors in Angular 20