Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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 auth

The 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().

Thanks for reading!

Donate to the site — we work hard for you.