Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Angular interceptors let you apply shared behavior around HttpClient calls, such as adding authentication headers, logging, handling errors, or observing responses. For new applications, Angular recommends functional interceptors configured with provideHttpClient(withInterceptors([...])). Clone requests to change them, treat the result of next(req) as an event stream, and test each interceptor with Angular’s HTTP testing utilities.

How Angular interceptors work

An interceptor sits between an HttpClient call and the next handler in the HTTP pipeline. It can modify an outgoing request, inspect or transform the response event stream, or return a response without forwarding the request. Interceptors are useful for behavior shared across calls, including authentication, logging, caching, retries, error handling, timing, and loading indicators. Angular’s interceptor guide describes these capabilities and their configuration.

For a request sent through a chain, each interceptor passes a request to the next handler. The response events then travel back through the chain, allowing interceptors to observe or transform them.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How to register a functional interceptor

Functional interceptors are Angular’s recommended option for predictable behavior, particularly in complex configurations. Register them in the application providers with provideHttpClient(withInterceptors([...])). The order in the array is the order requests enter the interceptors.

import { provideHttpClient, withInterceptors } from '@angular/common/http';

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

Functional interceptors run in the injection context of the injector where they are registered, so they can use Angular’s inject() function to obtain services. See Angular’s HTTP setup guide for provider configuration details.

Add an authentication header safely

Use req.clone() to create a modified request. Request fields such as headers are immutable; header update methods return a new headers value rather than changing the existing one.

import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = inject(AuthService).getAuthToken();
  const authenticatedReq = req.clone({
    headers: req.headers.set('Authorization', `Bearer ${token}`),
  });

  return next(authenticatedReq);
};

AuthService and the credential format are application-specific. Attach credentials only to the intended API destination; an interceptor should not send a secret indiscriminately to every URL used by the app. Angular’s example illustrates cloning and dependency injection, not an application’s security policy.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How to use class-based interceptors

Angular continues to support interceptors implemented as injectable classes that implement HttpInterceptor. Register the class with the HTTP_INTERCEPTORS multi-provider and enable DI-based interceptors using withInterceptorsFromDi().

import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { HTTP_INTERCEPTORS } from '@angular/common/http';

providers: [
  provideHttpClient(withInterceptorsFromDi()),
  {
    provide: HTTP_INTERCEPTORS,
    useClass: LegacyAuthInterceptor,
    multi: true,
  },
]

This approach can suit an existing application already organized around injectable interceptor classes. Angular notes that ordering can be harder to predict in extensive or hierarchical dependency-injection configurations, which is why it recommends functional interceptors for new code. The API reference for withInterceptorsFromDi() documents this setup.

How to handle responses and errors

Recognize the final response event

next(req) returns an Observable of HttpEvent values, not just a response body. Depending on the request and configuration, the stream can include lifecycle events such as progress notifications as well as the completed response. Check event.type against HttpEventType.Response when logic should run only for the final response.

import { HttpEventType, HttpInterceptorFn } from '@angular/common/http';
import { tap } from 'rxjs';

export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
  return next(req).pipe(
    tap(event => {
      if (event.type === HttpEventType.Response) {
        console.log(req.method, req.url, event.status);
      }
    })
  );
};

For a caller that needs status, headers, and body, use observe: 'response' in the HttpClient request. By default, HttpClient returns the response body. Interceptors still work with the event stream around that request. Angular’s guide to making requests explains response observation and request options.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Handle failures in the Observable error channel

Network or connection failures, configured timeout failures, and unsuccessful backend responses are reported as HttpErrorResponse values through the Observable error channel. Network and timeout failures have status 0; a backend failure carries the status returned by the server and its error response. Use RxJS error-handling operators when an interceptor needs shared handling, and rethrow failures when callers must handle them themselves.

import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';

export const errorInterceptor: HttpInterceptorFn = (req, next) => {
  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      // Apply shared reporting or handling here.
      return throwError(() => error);
    })
  );
};

Do not assume every failure is a server response: status 0 generally indicates a network or timeout problem, while a nonzero status identifies a backend response. See Angular’s request guide for the documented error behavior.

Immutability, retries, and interceptor metadata

HttpRequest and HttpResponse are mostly immutable, so create a clone to change request fields. Headers and parameters also use immutable update APIs: for example, set() replaces a value and append() adds one.

Request and response bodies are an exception: Angular does not deeply freeze them. Avoid mutating a body in place, especially if a request may be retried. An interceptor can execute again for a retry, and a prior in-place body change may therefore be applied or observed more than once.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For data intended only as interceptor metadata, use a typed HttpContextToken. Unlike most request fields, HttpContext is mutable, which allows state to be carried across retries. That mutable context is distinct from changing the request body.

When to return a synthetic response

An interceptor can return an HttpResponse directly instead of calling next(req), for example when serving a cached result. This short-circuits the chain: interceptors downstream from the one returning the synthetic response do not run, and the request does not reach the backend through that path. Consider which cross-cutting behaviors must still occur before using this pattern.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How to test an interceptor

Angular’s HTTP testing utilities let you issue a request, capture it, assert the interceptor’s changes, and simulate success or failure without sending a real network request. The official guide demonstrates testing one interceptor at a time.

  1. Configure the test providers with provideHttpClient(withInterceptors([interceptorUnderTest])) and provideHttpClientTesting().

    What’s actually slowing this PC down?

    Pick the symptom - the matching free tool is one click away.

    Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  2. Call the service or code that makes the HTTP request.

  3. Use HttpTestingController to capture the request and assert the relevant URL, headers, parameters, or other modified fields.

  4. Use the captured request’s flush() method to supply a representative success response or backend error response.

  5. Use the testing controller’s network-error mechanism when testing connection failures, then verify there are no unexpected outstanding requests.

    Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For complete test setup and API details, see Angular’s HTTP testing guide.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.