In Angular, an HTTP interceptor serves as a powerful mechanism to inspect and transform HTTP requests and responses globally before they are sent or processed. These interceptors are classes that implement the HttpInterceptor
interface, enabling developers to centralize common functionalities across all HTTP communications within an application.
What is an HTTP Interceptor?
An Angular HTTP interceptor acts as a middleware layer in the HTTP communication pipeline. When your Angular application makes an HTTP request, it doesn't go directly to the backend. Instead, it passes through a chain of registered interceptors. Similarly, the response from the backend goes through this same chain in reverse before reaching the component that initiated the request.
Their primary functions include:
- Adding Headers: Automatically appending headers, such as authentication tokens (e.g.,
Authorization: Bearer [token]
) or custom headers, to outgoing requests. - Handling Errors: Centralizing error management for HTTP responses, allowing you to catch and process common error scenarios (e.g., 401 Unauthorized, 500 Internal Server Error) in one place.
- Modifying Request/Response Data: Altering the request body or parameters before sending, or transforming the response data before it reaches the subscribing component.
- Logging: Intercepting requests and responses to log details like URL, method, status codes, and timing for debugging or monitoring purposes.
- Authentication: Streamlining the process of attaching authentication credentials to requests.
- Caching: Implementing custom caching strategies for HTTP responses.
- Showing Loading Indicators: Managing global loading spinners or progress bars for all network activity.
Why Use HTTP Interceptors?
Interceptors provide a clean and efficient way to handle cross-cutting concerns related to HTTP communication, promoting better code organization and reusability.
Advantage | Description | Example Scenario |
---|---|---|
Centralized Logic | Apply logic to all requests/responses from a single point, avoiding repetitive code in multiple services. | Adding an Authorization header to every API call. |
Modularity & Reusability | Create distinct interceptors for specific tasks, which can be reused across different projects. | A LoggerInterceptor can be used in any Angular application. |
Separation of Concerns | Keep HTTP-related infrastructure logic separate from your business logic in components and services. | Error handling logic resides in an interceptor, not components. |
Improved Maintainability | Easier to update or modify global HTTP behaviors without touching numerous service calls. | Changing how authentication tokens are refreshed. |
For more details on how Angular handles HTTP, you can refer to the official Angular HTTP client documentation.
Common Use Cases and Examples
HTTP interceptors are incredibly versatile and can address a wide range of application requirements.
Authentication and Authorization
One of the most common uses is to attach an authentication token to every outgoing request.
- Scenario: Your application requires an
Authorization
header with a JWT token for all protected API endpoints. - Solution: An interceptor can retrieve the token (e.g., from local storage) and add it to the
headers
of the request.
import { Injectable } from '@angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent,
} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const authToken = localStorage.getItem('token'); // Get token from storage
if (authToken) {
// Clone the request and add the authorization header
const authRequest = request.clone({
setHeaders: {
Authorization: `Bearer ${authToken}`,
},
});
return next.handle(authRequest);
}
return next.handle(request);
}
}
Error Handling
Centralizing error handling allows for consistent user feedback and robust error management.
- Scenario: Display a global notification or redirect the user upon specific HTTP error codes (e.g., 401, 403, 500).
- Solution: An interceptor can catch errors using
catchError
fromrxjs/operators
.
import { Injectable } from '@angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent,
HttpErrorResponse,
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Router } from '@angular/router'; // For redirection
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private router: Router) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
let errorMessage = 'An unknown error occurred!';
if (error.error instanceof ErrorEvent) {
// Client-side errors
errorMessage = `Error: ${error.error.message}`;
} else {
// Server-side errors
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
if (error.status === 401 || error.status === 403) {
console.error('Authentication/Authorization error:', error);
// Example: Redirect to login page
this.router.navigate(['/login']);
}
}
console.error(errorMessage);
return throwError(() => new Error(errorMessage));
})
);
}
}
Logging
Monitoring HTTP traffic can be crucial for debugging and performance analysis.
- Scenario: Log details of all incoming and outgoing HTTP requests for monitoring purposes.
- Solution: An interceptor can log request details before passing them and response details after receiving them.
Loading Indicators
Manage the visibility of a global loading spinner without scattering logic throughout components.
- Scenario: Show a loading spinner when any HTTP request is pending and hide it when all requests complete.
- Solution: An interceptor can increment a counter on request start and decrement on response/error, showing/hiding the spinner based on the counter.
How to Create and Register an Angular HTTP Interceptor
Implementing an HTTP interceptor involves two main steps:
1. Create the Interceptor Class
Create a TypeScript file (e.g., src/app/interceptors/my.interceptor.ts
) and define a class that implements the HttpInterceptor
interface. This interface requires a single method: intercept()
.
The intercept
method takes two arguments:
request
: The outgoingHttpRequest
. You should alwaysclone()
the request if you intend to modify it, as requests are immutable.next
: AnHttpHandler
that represents the next interceptor in the chain or the backend itself. Callnext.handle(modifiedRequest)
to pass the request along.
// my.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';
@Injectable()
export class MyInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
console.log('Intercepting request:', request.url);
const modifiedRequest = request.clone({
// Example: add a custom header
headers: request.headers.set('X-Custom-Header', 'AngularApp'),
});
return next.handle(modifiedRequest).pipe(
finalize(() => {
console.log('Request/Response chain completed for:', request.url);
// This block runs when the request is complete (success or error)
})
);
}
}
2. Register the Interceptor
Once you have created your interceptor, you need to register it with Angular's dependency injection system. This is typically done in your app.module.ts
file.
You must provide the interceptor using the HTTP_INTERCEPTORS
token from @angular/common/http
. It's crucial to set multi: true
because there can be multiple interceptors, and you're adding one more to the array of providers.
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { MyInterceptor } from './interceptors/my.interceptor'; // Import your interceptor
import { AuthInterceptor } from './interceptors/auth.interceptor';
import { ErrorInterceptor } from './interceptors/error.interceptor';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MyInterceptor,
multi: true, // This is crucial for providing multiple interceptors
},
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: ErrorInterceptor,
multi: true,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
The order in which you provide the interceptors matters. They are executed in the order they are provided in the providers
array for requests, and in reverse order for responses.
HTTP interceptors are a fundamental feature in Angular for building robust and maintainable applications that interact heavily with backend services. They empower developers to manage HTTP communication efficiently and consistently across the entire application.