This comprehensive post explains how to use HTTP interceptors in Angular. It includes only text, headings, and code — no icons or images. The post covers setup, configuration, real-world use cases, error handling, authentication, logging, request modification, chaining interceptors, testing, and best practices.
Table of Contents
- Introduction to HTTP Interceptors
- Why Use Interceptors in Angular
- Setting Up HttpClientModule
- Creating a Basic Interceptor
- Registering an Interceptor
- Modifying Requests
- Adding Authentication Tokens
- Global Error Handling
- Logging Requests and Responses
- Handling Loading Indicators
- Retrying Failed Requests
- Response Transformation
- Multiple Interceptors and Order of Execution
- Conditionally Applying Interceptors
- Excluding Specific Requests
- Using Dependency Injection in Interceptors
- Performance Considerations
- Testing HTTP Interceptors
- Real-World Examples
- Best Practices and Summary
1. Introduction to HTTP Interceptors
HTTP interceptors in Angular provide a powerful mechanism to intercept and modify HTTP requests or responses before they reach the server or the application logic. They are part of Angular’s HttpClient
module and are useful for tasks such as authentication, logging, error handling, and caching.
2. Why Use Interceptors in Angular
- Automatically attach authentication tokens
- Centralize error handling
- Log HTTP traffic globally
- Manage request headers
- Transform or cache responses
- Retry failed requests
- Control loading indicators for network calls
Interceptors act as middleware between your app and external APIs.
3. Setting Up HttpClientModule
Before using interceptors, ensure HttpClientModule
is imported in your root or feature module.
import { NgModule } from ‘@angular/core’;
import { BrowserModule } from ‘@angular/platform-browser’;
import { HttpClientModule, HTTP_INTERCEPTORS } from ‘@angular/common/http’;
import { AppComponent } from ‘./app.component’;
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule],
bootstrap: [AppComponent]
})
export class AppModule {}
4. Creating a Basic Interceptor
The simplest interceptor logs each outgoing request and incoming response.
import { Injectable } from ‘@angular/core’;
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from ‘@angular/common/http’;
import { Observable, tap } from ‘rxjs’;
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log(‘Request:’, req.url);
return next.handle(req).pipe(
tap(event => console.log(‘Response event:’, event))
);
}
}
5. Registering an Interceptor
Interceptors are provided globally through the HTTP_INTERCEPTORS
multi-provider token.
import { HTTP_INTERCEPTORS } from ‘@angular/common/http’;
import { LoggingInterceptor } from ‘./interceptors/logging.interceptor’;
@NgModule({
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true }
]
})
export class AppModule {}
The multi: true
flag allows multiple interceptors to be active simultaneously.
6. Modifying Requests
You can clone and modify requests because HttpRequest
objects are immutable.
@Injectable()
export class HeaderInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
const cloned = req.clone({ setHeaders: { ‘X-Custom-Header’: ‘AngularApp’ } });
return next.handle(cloned);
}
}
7. Adding Authentication Tokens
A common use of interceptors is attaching JWT or Bearer tokens to all outgoing requests.
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
const token = localStorage.getItem(‘auth_token’);
if (token) {
const cloned = req.clone({ setHeaders: { Authorization: Bearer ${token}
} });
return next.handle(cloned);
}
return next.handle(req);
}
}
You can extend this to refresh tokens automatically when expired.
8. Global Error Handling
Intercept all HTTP errors to log them or display global notifications.
import { catchError } from ‘rxjs/operators’;
import { throwError } from ‘rxjs’;
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
return next.handle(req).pipe(
catchError(err => {
console.error(‘HTTP Error:’, err);
alert(Error: ${err.status} - ${err.message}
);
return throwError(() => err);
})
);
}
}
9. Logging Requests and Responses
Centralize network logging for debugging.
@Injectable()
export class NetworkLoggerInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
const start = Date.now();
return next.handle(req).pipe(
tap({
next: () => console.log(Request to ${req.url} took ${Date.now() - start}ms
),
error: (err) => console.warn(Error on ${req.url}:
, err)
})
);
}
}
10. Handling Loading Indicators
Show and hide loading spinners globally.
@Injectable()
export class LoadingInterceptor implements HttpInterceptor {
private requests: HttpRequest<any>[] = [];
removeRequest(req: HttpRequest<any>) {
const i = this.requests.indexOf(req);
if (i >= 0) {
this.requests.splice(i, 1);
}
this.isLoading = this.requests.length > 0;
}
isLoading = false;
intercept(req: HttpRequest<any>, next: HttpHandler) {
this.requests.push(req);
this.isLoading = true;
return next.handle(req).pipe(
finalize(() => this.removeRequest(req))
);
}
}
Bind isLoading
to your spinner component.
11. Retrying Failed Requests
Use interceptors for retry logic on transient errors.
import { retry, catchError } from ‘rxjs/operators’;
@Injectable()
export class RetryInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
return next.handle(req).pipe(
retry(3),
catchError(err => throwError(() => err))
);
}
}
12. Response Transformation
You can transform server responses before components receive them.
import { map } from ‘rxjs/operators’;
@Injectable()
export class TransformInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
return next.handle(req).pipe(
map(event => {
// modify response if needed
return event;
})
);
}
}
13. Multiple Interceptors and Order of Execution
Interceptors run in the order they are provided and return responses in reverse order.
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true }
]
In this case, AuthInterceptor
executes before LoggingInterceptor
for outgoing requests, but LoggingInterceptor
handles responses first.
14. Conditionally Applying Interceptors
Sometimes you may only want to apply interceptors for specific domains or endpoints.
if (req.url.startsWith(‘https://api.example.com’)) {
const cloned = req.clone({ setHeaders: { ‘X-Special’: ‘Yes’ } });
return next.handle(cloned);
}
return next.handle(req);
15. Excluding Specific Requests
You can exclude requests such as authentication or static assets.
if (req.url.includes(‘/auth/login’)) {
return next.handle(req);
}
This prevents adding headers or logging for those requests.
16. Using Dependency Injection in Interceptors
Interceptors can inject services for advanced logic.
constructor(private authService: AuthService, private logger: LoggerService) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
const token = this.authService.getToken();
const cloned = req.clone({ setHeaders: { Authorization: Bearer ${token}
} });
this.logger.log(‘Authenticated request sent’);
return next.handle(cloned);
}
17. Performance Considerations
- Keep interceptor logic lightweight.
- Avoid heavy computation or blocking operations.
- Use conditionals to limit execution to relevant URLs.
- Chain interceptors carefully to avoid infinite loops.
18. Testing HTTP Interceptors
Test interceptors using HttpClientTestingModule
and HttpTestingController
.
import { TestBed } from ‘@angular/core/testing’;
import { HTTP_INTERCEPTORS, HttpClientTestingModule, HttpTestingController } from ‘@angular/common/http/testing’;
import { AuthInterceptor } from ‘./auth.interceptor’;
import { HttpClient } from ‘@angular/common/http’;
describe(‘AuthInterceptor’, () => {
let httpMock: HttpTestingController;
let httpClient: HttpClient;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]
});
httpMock = TestBed.inject(HttpTestingController);
httpClient = TestBed.inject(HttpClient);
});
it(‘should add Authorization header’, () => {
localStorage.setItem(‘auth_token’, ‘abc123’);
httpClient.get(‘/test’).subscribe();
const req = httpMock.expectOne(‘/test’);
expect(req.request.headers.has(‘Authorization’)).toBeTrue();
});
});
19. Real-World Examples
Global Error Logging + Auth Header Combination
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }
]
This ensures tokens are added first and errors are caught globally.
Refresh Token Flow
if (err.status === 401) {
return this.authService.refreshToken().pipe(
switchMap(() => next.handle(this.addToken(req)))
);
}
Leave a Reply