Angular’s HttpClient offers a powerful feature called interceptors, which act as middleware for HTTP requests and responses. In this guide, we’ll explore everything you need to know about interceptors, from basic concepts to advanced techniques.
Understanding Interceptors
Interceptors in HttpClient are functions or classes that can intercept outgoing HTTP requests and incoming responses. Interceptors act as middleware for HTTP requests and responses.
Interceptors are like helpers for handling HTTP requests and responses in Angular.
Common use cases for interceptors
- Adding authentication headers
- Retrying failed requests
- Caching responses
- Measuring server response times and log them
- Driving UI elements such as a loading spinner while network operations are in progress
Types of Interceptors
HttpClient supports two main types of interceptors:
· Functional Interceptors
· DI-based Interceptors
Both types of interceptors have access to the outgoing request and can modify it before it is sent. They can also intercept the response before it reaches the application code.
1. Functional Interceptors:
These are functions that accept the outgoing request and a next function representing the next step in the interceptor chain. They are preferred for their predictable behavior.
Defining a Functional Interceptor
This interceptor logs the outgoing request URL before forwarding it to the next step in the chain and handles errors.
export const loggingInterceptorFunctional: HttpInterceptorFn = (req, next) => {
console.log('Request URL: ' + req.url);
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
console.error('Logging Interceptor Functional Error:', error);
return throwError(()=> error);
})
);
}
Configuring Functional Interceptors
Functional Interceptors are configured during HttpClient setup using the withInterceptors feature:
bootstrapApplication(AppComponent, {providers: [
provideHttpClient(
withInterceptors([loggingInterceptor, cachingInterceptor]),
)
]});
Here, we configure both loggingInterceptor and cachingInterceptor. They will form a chain where loggingInterceptor processes the request before cachingInterceptor.
Intercepting Response Events:
Interceptors can transform the stream of HttpEvents returned by next, allowing access to or manipulation of the response.
export const loggingInterceptorFunctional: HttpInterceptorFn = (req, next) => {
console.log('Request URL: ' + req.url);
return next(req).pipe(tap(event => {
if (event.type === HttpEventType.Response) {
console.log(req.url, 'returned a response with status', event.status);
}
}));
}
2. DI-based Interceptors:
These are injectable classes that implement the HttpInterceptor interface. They offer similar capabilities to functional interceptors but are configured differently through Angular’s Dependency Injection system.
Defining a DI-based Interceptor
DI-based interceptors are defined as injectable classes implementing the HttpInterceptor interface.
This interceptor logs the outgoing request URL before forwarding it to the next step in the chain.
@Injectable()
export class LoggingInterceptorDI implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log('Request URL: ' + req.url);
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
console.error('Logging Interceptor DI Error:', error);
return throwError(()=> error);
})
);
}
}
Configuring DI-based Interceptors
DI-based interceptors are configured through Angular’s Dependency Injection system:
bootstrapApplication(AppComponent, {providers: [
provideHttpClient(
withInterceptorsFromDi(),
),
{provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true},
]});
Example
Now let’s create a simple Angular project where we’ll implement both functional and DI-based interceptors
This simplified example covers all the functionalities mentioned: adding authentication headers, retrying failed requests, measuring server response times, loading spinner while network operations are in progress and logging.
Run the following command to generate a new project:
ng new interceptors-demo
Run the following command to generate a new service:
ng generate service auth
Now, let’s modify the auth.service.ts file in the src/app directory to manage authentication and retrieve the authentication token
// auth.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class AuthService {
getAuthToken(): string {
// Logic to retrieve authentication token
return 'your_auth_token_here';
}
}
Run the following command to generate a new service:
ng generate service loading
Now, let’s modify the loading.service.ts file in the src/app directory to show and hide the loading spinner UI element.
import { Injectable } from '@angular/core';
@Injectable()
export class LoadingService {
private loading = false;
showLoadingSpinner() {
this.loading = true;
console.log('Loading spinner shown'); // Log when loading spinner is shown
// Logic to show loading spinner UI element
}
hideLoadingSpinner() {
this.loading = false;
console.log('Loading spinner hidden'); // Log when loading spinner is hidden
// Logic to hide loading spinner UI element
}
isLoading(): boolean {
return this.loading;
}
}
Run the following command to generate a new interceptor:
ng generate interceptor functional
Now, let’s modify the functional.intercepor.ts file in the src/app directory to implement functional interceptors:
import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { throwError } from 'rxjs';
import { catchError, finalize, retry } from 'rxjs/operators';
import { LoadingService } from './loading.service';
// Server Response Time Interceptor
export const responseTimeInterceptorFunctional: HttpInterceptorFn = (req, next) => {
const startTime = Date.now();
return next(req).pipe(
finalize(() => {
const endTime = Date.now();
const responseTime = endTime - startTime;
console.log(`Request to ${req.url} took ${responseTime}ms`);
})
);
}
// Loading Spinner Interceptor
export const loadingSpinnerInterceptorFunctional: HttpInterceptorFn = (req, next) => {
const loadingService = new LoadingService(); // Instantiate the loading service
loadingService.showLoadingSpinner(); // Show loading spinner UI element
return next(req).pipe(
finalize(() => {
loadingService.hideLoadingSpinner(); // Hide loading spinner UI element
})
);
};
export const authInterceptorFunctional: HttpInterceptorFn = (req, next) => {
const authToken = 'YOUR_AUTH_TOKEN_HERE';
// Clone the request and add the authorization header
const authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${authToken}`
}
});
// Pass the cloned request with the updated header to the next handler
return next(authReq);
};
export const retryInterceptorFunctional: HttpInterceptorFn = (req, next) => {
const maxRetries = 3;
return next(req).pipe(
retry(maxRetries),
catchError((error: HttpErrorResponse) => {
console.error('Retry Interceptor Functional Error:', error);
return throwError(()=> error);
})
);
};
export const loggingInterceptorFunctional: HttpInterceptorFn = (req, next) => {
console.log('Request URL: ' + req.url);
return next(req);
}
Run the following command to generate a new interceptor:
ng generate interceptor dibased
Now, let’s modify the dibased.intercepor.ts file in the src/app directory to implement DI-based interceptors:
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpInterceptor, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, finalize, retry, tap } from 'rxjs/operators';
import { LoadingService } from './loading.service';
import { AuthService } from './auth.service';
@Injectable()
export class ResponseTimeInterceptorDI implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const startTime = Date.now();
return next.handle(req).pipe(
tap(event => {
if (event instanceof HttpResponse) {
const endTime = Date.now();
const responseTime = endTime - startTime;
console.log(`Request to ${req.url} took ${responseTime}ms`);
}
})
);
}
}
@Injectable()
export class LoadingSpinnerInterceptorDI implements HttpInterceptor {
constructor(private loadingService: LoadingService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.loadingService.showLoadingSpinner(); // Show loading spinner UI element here
return next.handle(req).pipe(
finalize(() => {
this.loadingService.hideLoadingSpinner(); // Hide loading spinner UI element
})
);
}
}
@Injectable()
export class RetryInterceptorDI implements HttpInterceptor {
constructor() {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const maxRetries = 3; // Customize max retry attempts here
return next.handle(req).pipe(
retry(maxRetries),
catchError((error: HttpErrorResponse) => {
console.error('Retry Interceptor DI Error:', error);
return throwError(()=> error);
})
);
}
}
@Injectable()
export class AuthInterceptorDI implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const authToken = this.authService.getAuthToken();
const authReq = req.clone({
headers: req.headers.set('Authorization', `Bearer ${authToken}`)
});
return next.handle(authReq);
}
}
@Injectable()
export class LoggingInterceptorDI implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log('Request URL: ' + req.url);
return next.handle(req);
}
}
Now, we’ll configure both interceptors in the app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptors, withInterceptorsFromDi } from '@angular/common/http';
import { authInterceptorFunctional, loadingSpinnerInterceptorFunctional, loggingInterceptorFunctional, responseTimeInterceptorFunctional, retryInterceptorFunctional } from './functional.interceptor';
import { AuthInterceptorDI, LoadingSpinnerInterceptorDI, LoggingInterceptorDI, ResponseTimeInterceptorDI, RetryInterceptorDI } from './dibased.interceptor';
import { LoadingService } from './loading.service';
import { AuthService } from './auth.service';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(
withInterceptors([
responseTimeInterceptorFunctional,
loadingSpinnerInterceptorFunctional,
authInterceptorFunctional,
retryInterceptorFunctional,
loggingInterceptorFunctional,
]),
/* THE COMMENTED CONFIGURATIONS ARE FOR DI-based INTERCEPTORS
Comment withInterceptors() and uncomment the below code to use DI-based interceptors
*/
//withInterceptorsFromDi(),
),
//LoadingService,
//AuthService,
// { provide: HTTP_INTERCEPTORS, useClass: ResponseTimeInterceptorDI, multi: true },
// { provide: HTTP_INTERCEPTORS, useClass: LoadingSpinnerInterceptorDI, multi: true },
// { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorDI, multi: true },
// { provide: HTTP_INTERCEPTORS, useClass: RetryInterceptorDI, multi: true },
// { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptorDI, multi: true }
]
};
Now, let’s modify the app.component.ts file to use Angular’s HttpClient to fetch data:
import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent implements OnInit{
constructor(private http: HttpClient) { }
ngOnInit() {
this.getData();
}
getData() {
this.http.get('https://jsonplaceholder.typicode.com/posts').subscribe({
next: data => {
console.log(data);
},
error: error => {
console.error('Error getting post:', error);
}
});
}
}
Finally, let’s update the app.component.html file to remove the default content:
<div>
<h1>Welcome to Angular Interceptors Demo</h1>
</div>
Now, you can run the application using the following command:
ng serve
This will start a development server, and you should be able to see the output in the browser console.
Conclusion
Interceptors are powerful tools for managing HTTP traffic within Angular applications. By intercepting requests and responses, developers can implement a wide range of functionalities, from authentication and caching to logging and error handling. Understanding and effectively utilizing interceptors can significantly enhance the reliability, maintainability, security, and performance of Angular applications.
To get the whole code, check the repo below👇👇👇
https://github.com/anthony-kigotho/interceptors-demo
CTA
Many developers and learners encounter tutorials that are either too complex or lacking in detail, making it challenging to absorb new information effectively.
Subscribe to our newsletter today, to access our comprehensive guides and tutorials designed to simplify complex topics and guide you through practical applications.
Top comments (2)
¡Gracias, amigo!
Me ayudaste a resolver un error que me tuvo horas dando vueltas.
thanks, to add if you want to make a retry interceptor only for server errors can use this retry config
retry({
Attempt ${retryAttempt}: retrying in ${retryAttempt * RETRY_DELAY}mscount: MAX_OF_RETRY,
delay: (error: HttpErrorResponse, retryAttempt: number): Observable<number> => {
// if maximum number of retries have been met
// or response is a status code we don't wish to retry, throw error
if (retryAttempt > MAX_OF_RETRY || error.status !== 404) {
return throwError(() => error);
}
console.log(
);
return timer(retryAttempt * RETRY_DELAY);
},
})