With the introduction of Angular 17 and new features like Signals and Standalone Components, state management and application structure have become simpler and more reactive. In this guide, I will show you how to implement an HTTP interceptor to display a dynamic loader during HTTP calls, leveraging
Signals for state management and Standalone Components for a leaner structure.
1. Create a Loader Service with Signals
Let’s start by creating a service that uses Signals to manage the loader’s state.
Step 1: Generate the Service
Run the following command:
ng generate service loader
Step 2: Implement the Service with Signals
In the loader.service.ts file, use signals to manage the loader state:
import { Injectable, signal } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class LoaderService {
isLoading = signal<boolean>(false);
show(): void {
this.isLoading.set(true);
}
hide(): void {
this.isLoading.set(false);
}
}
2. Create a Standalone Component for the Loader
Now, create a Standalone Component that displays the loader when isLoading is true.
Step 1: Generate the Component
Run the following command:
ng generate component loader --standalone
Step 2: Implement the Component
In the loader.component.ts file, import the service and use isLoading to control the loader’s visibility:
import { Component, inject } from '@angular/core';
import { LoaderService } from './loader.service';
import { ProgressSpinnerModule } from 'primeng/progressspinner';
@Component({
selector: 'app-loader',
standalone: true,
imports: [ProgressSpinnerModule],
template: `
@if (loaderService.isLoading()) {
<div class="loader-overlay">
<div class="loader-spinner">
<p-progressSpinner></p-progressSpinner>
</div>
</div>
}
`,
styles: [`
.loader-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.loader-spinner {
color: white;
}
`]
})
export class LoaderComponent {
loaderService = inject(LoaderService);
}
3. Create the HTTP Interceptor
Now, let’s create the interceptor that will intercept all HTTP calls and update the loader state.
Step 1: Generate the Interceptor
Run the following command:
ng generate interceptor loader
Step 2: Implement the Interceptor
In the loader.interceptor.ts file, implement the logic to show and hide the loader:
import { Injectable, inject } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { LoaderService } from './loader.service';
@Injectable()
export class LoaderInterceptor implements HttpInterceptor {
private loaderService = inject(LoaderService);
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
// Mostra il loader
this.loaderService.show();
return next.handle(request).pipe(
finalize(() => {
// Nasconde il loader quando la richiesta è completata
this.loaderService.hide();
})
);
}
}
4. Configure the Interceptor in a Standalone Application
With Angular 17 and Standalone Components, the interceptor configuration is done directly in the main.ts or app.config.ts file.
Step 1: Configure the Interceptor in app.config.ts
Create or modify the app.config.ts file to include the interceptor:
import { ApplicationConfig, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { LoaderInterceptor } from './loader.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(withInterceptorsFromDi()),
{ provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true }
]
};
5. Include the Loader Component in the App
Now, include the LoaderComponent in the main application template.
Step 1: Add the Component in app.component.ts
Make sure app.component.ts is a Standalone Component and imports LoaderComponent:
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { LoaderComponent } from './loader/loader.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, LoaderComponent],
template: `
<app-loader></app-loader>
<router-outlet></router-outlet>
`,
})
export class AppComponent {}
6. Test the Implementation
Now, whenever an HTTP call is made, the loader will automatically appear until the request completes.
Example of an HTTP Call
Here is an example service that makes an HTTP call:
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataService {
private http = inject(HttpClient);
private apiUrl = 'https://jsonplaceholder.typicode.com/posts';
getPosts(): Observable<any> {
return this.http.get(this.apiUrl);
}
}
When you call getPosts() from a component, the loader will automatically appear and disappear once the request is finished.
7. Optimizations and Considerations
Exclude Certain Requests
If you don’t want the loader to appear for some requests (e.g., polling calls or background requests), you can modify the interceptor to ignore them:
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
// Ignora le richieste con un header specifico
if (request.headers.get('skip-loader')) {
return next.handle(request);
}
this.loaderService.show();
return next.handle(request).pipe(
finalize(() => {
this.loaderService.hide();
})
);
}
Error Handling
If you want to ensure that the loader is hidden even in case of an error, you can use the catchError operator:
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
// ...
return next.handle(request).pipe(
catchError(error => {
this.loaderService.hide();
return throwError(() => error);
}),
finalize(() => {
this.loaderService.hide();
})
);
Conclusion
With Angular 17, Signals, and Standalone Components, managing a dynamic loader during HTTP calls becomes simpler and more reactive. By using an HTTP interceptor and a service based on Signals, you can:
- Centralize the loader display logic.
- Avoid code duplication.
- Improve user experience with immediate visual feedback.
This solution is modern, efficient, and scalable,perfect for Angular applications aiming for maximum reactivity and usability.