Skip to content

Angular HTTP Interceptors

Interceptors let you inspect and modify every HTTP request and response passing through HttpClient, ideal for cross-cutting concerns like authentication and logging. This lesson covers the modern functional interceptor API.

What Is an HTTP Interceptor?

An interceptor sits in the middle of every outgoing request and incoming response, letting you transform the request before it's sent, or the response (or error) before it reaches your application code, without touching every individual service method.

Modern Angular uses functional interceptors, plain functions matching the HttpInterceptorFn signature, registered with provideHttpClient(withInterceptors([...])).

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const auth = inject(AuthService);
  const token = auth.getToken();

  const authReq = token
    ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
    : req;

  return next(authReq);
};

// app.config.ts
provideHttpClient(withInterceptors([authInterceptor]))

req.clone() creates a new immutable request with the added header; the original request object is never mutated directly.

The Functional Interceptor Signature

export const myInterceptor: HttpInterceptorFn = (req, next) => {
  // inspect/modify req
  return next(req).pipe(
    // inspect/transform the response or error
  );
};

provideHttpClient(withInterceptors([myInterceptor]))
  • req is the outgoing HttpRequest; requests are immutable, use .clone() to change anything.
  • next forwards the request to the next interceptor in the chain (or the actual HTTP call, if it's the last one).
  • The return value is an Observable of the eventual response, which you can further transform with RxJS operators.
  • Interceptors run in the order they're listed in withInterceptors([...]).

Interceptors Cheatsheet

Common interceptor tasks and their implementation pattern.

Task Approach
Attach an auth token Clone the request with an added Authorization header
Log every request/response Wrap next(req) with tap() to log timing/status
Handle 401 globally catchError checking error.status === 401, then redirect to login
Show a global loading spinner Increment/decrement a shared loading counter around next(req)
Add a base URL to relative paths Clone the request with a rewritten url
Register interceptors provideHttpClient(withInterceptors([a, b, c]))

A Logging Interceptor

A logging interceptor is a good first example: it doesn't modify the request or response, only observes them as they pass through, useful for debugging or basic analytics.

export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
  const started = Date.now();
  return next(req).pipe(
    tap({
      next: () => console.log(`${req.method} ${req.url} took ${Date.now() - started}ms`),
      error: () => console.log(`${req.method} ${req.url} failed after ${Date.now() - started}ms`),
    })
  );
};

A Global Error-Handling Interceptor

An error-handling interceptor can catch specific HTTP status codes application-wide, redirecting to a login page on 401 Unauthorized, for example, without repeating that logic in every service.

export const authErrorInterceptor: HttpInterceptorFn = (req, next) => {
  const router = inject(Router);

  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      if (error.status === 401) {
        router.navigate(['/login']);
      }
      return throwError(() => error);
    })
  );
};

Interceptor Execution Order

Interceptors execute in the order listed in withInterceptors([...]) for the outgoing request, and in reverse order as the response travels back, similar to middleware chains in server frameworks.

provideHttpClient(
  withInterceptors([loggingInterceptor, authInterceptor, authErrorInterceptor])
)

Common Mistakes

  • Mutating req directly instead of using .clone(), which Angular's HttpRequest does not allow (requests are immutable).
  • Forgetting to call next(req) (or returning early without it), which silently drops the request entirely.
  • Attaching auth headers to every request indiscriminately, including calls to third-party or public endpoints that shouldn't receive your token.
  • Registering interceptors in the wrong order, causing an error-handling interceptor to run before the interceptor that would have retried or refreshed a token.

Key Takeaways

  • Interceptors intercept every outgoing request and incoming response through HttpClient.
  • Modern Angular uses functional interceptors registered via withInterceptors([...]).
  • Requests are immutable, use req.clone() to modify headers, URL, or body.
  • Interceptors are ideal for cross-cutting concerns: auth tokens, logging, and global error handling.

Pro Tip

Guard auth-token interceptors with a URL check (like only attaching a token to requests matching your own API's base URL), so you never accidentally leak an authorization header to a third-party service you also call via HttpClient.