HTTP Interceptor
| Since: | Angular 14(2022) |
|---|
An HTTP interceptor in Angular intercepts outgoing requests and incoming responses globally, allowing you to add authorization headers, handle errors centrally, or log traffic without modifying individual service classes. Angular 15+ introduced a function-based interceptor (HttpInterceptorFn) in addition to the traditional class-based approach (HttpInterceptor). Function-based interceptors are the default style since Angular 15.
Function-based Interceptor (Angular 15+)
// auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = localStorage.getItem('token');
const authReq = token
? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
: req;
return next(authReq);
};
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './auth.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([authInterceptor])),
],
};
Class-based Interceptor
// auth.interceptor.ts (class-based)
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(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = localStorage.getItem('token');
const authReq = token
? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
: req;
return next.handle(authReq);
}
}
Sample Code
An error handling interceptor that converts HTTP errors into user-friendly messages.
// error-handling.interceptor.ts
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';
export const errorHandlingInterceptor: HttpInterceptorFn = (req, next) => {
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
let message = 'An unexpected error occurred.';
if (error.status === 401) {
message = 'Authentication required. Please log in.';
} else if (error.status === 403) {
message = 'Access denied.';
} else if (error.status === 404) {
message = 'Resource not found.';
} else if (error.status >= 500) {
message = 'Server error. Please try again later.';
}
console.error(message, error);
return throwError(() => new Error(message));
})
);
};
A logging interceptor that records request timing.
// logging.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { tap } from 'rxjs';
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
const start = Date.now();
console.log(`[HTTP] ${req.method} ${req.url}`);
return next(req).pipe(
tap({
next: () => console.log(`[HTTP] ${req.method} ${req.url} — ${Date.now() - start}ms`),
error: (err) => console.error(`[HTTP] Error: ${err.message}`),
})
);
};
Registering multiple interceptors. They run in the order they are listed.
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './auth.interceptor';
import { loggingInterceptor } from './logging.interceptor';
import { errorHandlingInterceptor } from './error-handling.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([
loggingInterceptor, // 1st: log the request
authInterceptor, // 2nd: add auth header
errorHandlingInterceptor, // 3rd: handle errors
])
),
],
};
Notes
| Item | Description |
|---|---|
| Request immutability | HttpRequest objects are immutable. Use req.clone({ ... }) to create a modified copy before passing it to next. |
| Must call next() | Every interceptor must call next(req) (function-based) or next.handle(req) (class-based) to pass the request along the chain. Omitting it silently swallows the request. |
| Interceptor order | Interceptors run in the order they are listed in the withInterceptors array for requests, and in reverse order for responses. |
| Skipping an interceptor | To skip an interceptor for specific requests, check req.url or use a custom context (HttpContext) inside the interceptor. |
Summary
HTTP interceptors provide a centralized place to add cross-cutting concerns to all HTTP requests and responses: authentication headers, error handling, logging, caching, and retry logic. Function-based interceptors (HttpInterceptorFn) were introduced in Angular 15 and are used with the Standalone API; class-based interceptors are still supported for NgModule projects.
Always clone the request with req.clone() before modifying it, and always call next() to continue the chain. For making HTTP requests in services, see HttpClient.
Common Mistake: Mutating the request object instead of cloning it
HttpRequest is immutable. You cannot set headers or modify properties directly. Always use req.clone({ ... }) to produce a modified copy.
// NG: mutating the request directly — the change is silently ignored
export const authInterceptor: HttpInterceptorFn = (req, next) => {
req.headers.set('Authorization', 'Bearer token'); // has no effect — HttpHeaders is immutable
return next(req);
};
// OK: use req.clone() to create a modified copy
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authReq = req.clone({ setHeaders: { Authorization: 'Bearer token' } });
return next(authReq);
};
Common Mistake: Forgetting to call next() — request never sent
If an interceptor does not call next(req), the request chain is broken and no HTTP request is sent. The component will wait forever for a response.
// NG: next() not called — request is swallowed
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
console.log(req.url);
// return next(req); // missing — request never sent
return EMPTY;
};
// OK: always return next(req)
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
console.log(req.url);
return next(req);
};
Common Mistake: Causing an infinite loop by making a request inside an interceptor without a skip condition
Making an HTTP request inside an interceptor (e.g., to refresh a token) triggers the same interceptor again, causing an infinite loop. Guard against it by checking the request URL or using HttpContext.
// NG: refreshing token inside the interceptor re-triggers itself
export const authInterceptor: HttpInterceptorFn = (req, next) => {
return next(req).pipe(
catchError(err => {
if (err.status === 401) {
return http.post('/auth/refresh', {}).pipe(...); // triggers authInterceptor again
}
return throwError(() => err);
})
);
};
// OK: skip the interceptor for the refresh endpoint
export const authInterceptor: HttpInterceptorFn = (req, next) => {
if (req.url.includes('/auth/refresh')) {
return next(req); // skip — no header, no retry
}
// normal interception logic
return next(req);
};
If you find any errors or copyright issues, please contact us.