100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
TypeScript

HTTP Interceptors

Understand how Angular's functional HTTP interceptors let you centrally attach headers, log requests, and handle errors for every outgoing request.

HTTP & RxJSIntermediate9 min readJul 9, 2026
Analogies

HTTP Interceptors

An interceptor sits between your application code and the network, given the chance to inspect or modify every outgoing HttpRequest and every incoming HttpEvent before it reaches its final destination. This makes interceptors the natural place to implement cross-cutting HTTP concerns that would otherwise need to be duplicated in every service call: attaching an Authorization header, adding a correlation ID for tracing, logging request/response timing, transforming error responses into a consistent shape, or globally redirecting to a login page on a 401. Because interceptors compose into a chain, you can register several of them and each one decides whether to pass the request along unchanged, modify it, short-circuit it, or transform the response coming back.

🏏

Cricket analogy: An interceptor is like the third umpire reviewing every close call before it's finalized — checking DRS footage, confirming no-ball lines, adding a consistent replay overlay — so individual on-field umpires don't each have to independently re-derive the same review logic.

Writing a functional interceptor

Modern Angular (v15+) favors functional interceptors — plain functions matching the HttpInterceptorFn signature — over the older class-based HttpInterceptor interface, though both still work. A functional interceptor receives the outgoing HttpRequest and a next function representing the rest of the chain; calling next(req) forwards the request (optionally cloned with modifications, since HttpRequest instances are immutable) and returns an Observable<HttpEvent<unknown>> that you can further pipe with RxJS operators.

🏏

Cricket analogy: Functional interceptors (v15+) are like the modern DRS protocol replacing the old informal umpire-consultation system: a lightweight review function receives the delivery and a 'continue' signal representing the rest of the over, and can clone the replay with an added slow-motion angle before passing it along, since the original footage itself is unchangeable.

typescript
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';

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

  if (!token) {
    return next(req);
  }

  const cloned = req.clone({
    setHeaders: { Authorization: `Bearer ${token}` },
  });
  return next(cloned);
};

Registering interceptors with provideHttpClient

Functional interceptors are registered via the withInterceptors() feature function passed to provideHttpClient(), and they run in the exact array order you provide — the first interceptor sees the request first on the way out, and sees the response last on the way back, like layers of an onion. This ordering matters: an auth interceptor that adds a token should typically run before a logging interceptor that reads headers for debugging output.

🏏

Cricket analogy: withInterceptors() ordering is like a fielding chain of relay throws from boundary to keeper: the first fielder (auth interceptor) who gathers the ball and adds their throw-tag should act before the reader further down the chain (logging interceptor), and on the return throw, the last fielder to touch it is the first the batsman sees.

typescript
// app.config.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './auth.interceptor';
import { errorInterceptor } from './error.interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([authInterceptor, errorInterceptor])),
  ],
};

Global error handling in an interceptor

A dedicated error-handling interceptor is a common pattern for centralizing behavior like redirecting to a login page on a 401, showing a global toast notification on a 500, or normalizing varied backend error shapes into one consistent structure your components can rely on. Piping the next(req) call through catchError lets the interceptor react to any failed request across the entire application without every individual service needing its own error-handling boilerplate.

🏏

Cricket analogy: A dedicated error-handling interceptor is like a designated senior umpire who handles every controversial dismissal review centrally — redirecting a wrongly given out to a review, flagging a serious pitch-invasion incident to ground security, and normalizing every stoppage report into one consistent match report format.

typescript
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { catchError, throwError } from 'rxjs';

export const errorInterceptor: HttpInterceptorFn = (req, next) => {
  const router = inject(Router);
  return next(req).pipe(
    catchError((err: HttpErrorResponse) => {
      if (err.status === 401) {
        router.navigate(['/login']);
      }
      return throwError(() => err);
    })
  );
};

Interceptors are conceptually similar to Express/Koa middleware or Axios interceptors in the React/Vue ecosystems, but they're baked into Angular's DI system — a functional interceptor can call inject() to pull in any injectable service, giving it full access to application state like auth tokens or feature flags without manual wiring.

HttpRequest and HttpResponse objects are immutable by design, so you can never mutate req.headers directly — always use req.clone({...}) to produce a modified copy. Forgetting this and trying to assign to a request's properties directly will fail silently or be a no-op, and the original unmodified request will be sent.

  • Interceptors sit between application code and the network, seeing every outgoing request and incoming response.
  • Functional interceptors (HttpInterceptorFn) are the modern approach and can inject() services directly.
  • Register interceptors with withInterceptors([...]) passed to provideHttpClient(); array order defines execution order.
  • HttpRequest is immutable — use req.clone({...}) to attach headers or modify a request, never direct mutation.
  • A dedicated error interceptor centralizes handling like redirecting on 401 or normalizing error shapes app-wide.
  • Interceptors execute like nested middleware: the first in the array is outermost on both the request and response path.

Practice what you learned

Was this page helpful?

Topics covered

#TypeScript#AngularStudyNotes#WebDevelopment#HTTPInterceptors#HTTP#Interceptors#Writing#Functional#Networking#StudyNotes#SkillVeris