HTTP interceptors
An interceptor is a function that every HttpClient request passes through before it is sent, and every response after it arrives. It does what all requests need at once: adds an authorization token, writes a log, shows a global loading indicator.
Creating an interceptor
ng generate interceptor authThe CLI creates the file src/app/auth-interceptor.ts (before version 20 — auth.interceptor.ts) with the authInterceptor function. Let's add an authorization header to every request:
// src/app/auth-interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authReq = req.clone({
setHeaders: { Authorization: 'Bearer demo-token' },
});
return next(authReq);
};The function receives the request req and the function next, which passes the request on. A request is immutable, so you copy it with clone() and the changes you need.
Registration
The interceptor starts working once it is listed in withInterceptors():
// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { routes } from './app.routes';
import { authInterceptor } from './auth-interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(withInterceptors([authInterceptor])),
],
};Open the post list and the Network tab in the developer tools: the request headers to jsonplaceholder.typicode.com now include Authorization: Bearer demo-token. The code of the services and components did not change.
Working with the response
next() returns an Observable with the response, and you can add operators to it. A logging interceptor measures the request time:
// src/app/logging-interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { tap } from 'rxjs';
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
const started = Date.now();
return next(req).pipe(
tap({
complete: () => console.log(`${req.method} ${req.url}: ${Date.now() - started} ms`),
}),
);
};After each request the browser console shows a line like GET https://jsonplaceholder.typicode.com/posts: 180 ms.
Several interceptors
// src/app/app.config.ts
provideHttpClient(withInterceptors([authInterceptor, loggingInterceptor])),A request passes the interceptors in array order, and the response comes back in reverse order.
Injecting services
Inside an interceptor you can call inject() — for example, to take the token from an auth service instead of hard-coding it:
// src/app/auth-interceptor.ts
import { inject } from '@angular/core';
import { HttpInterceptorFn } from '@angular/common/http';
import { Auth } from './auth';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = inject(Auth).token();
if (!token) {
return next(req);
}
return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));
};We will build the Auth service with a token signal in the route guards lesson.
Common mistake
Changing the request directly: req.headers.set('Authorization', ...). The set() method returns a new headers object, while the original request stays unchanged — the header never reaches the server. Always use req.clone() and pass the copy to next().