HTTP - Intercept requests and responses

With interception, you declare interceptors that inspect and transform HTTP requests from your application to a server. The same interceptors can also inspect and transform a server's responses on their way back to the application. Multiple interceptors form a forward-and-backward chain of request/response handlers.

Interceptors can perform a variety of implicit tasks, from authentication to logging, in a routine, standard way, for every HTTP request/response.

Without interception, developers would have to implement these tasks explicitly for each HttpClient method call.

Write an interceptor

To implement an interceptor, declare a class that implements the intercept() method of the HttpInterceptor interface.

Here is a do-nothing noop interceptor that passes the request through without touching it:

import { Injectable } from '@angular/core';
import {
  HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
} from '@angular/common/http';

import { Observable } from 'rxjs';

/** Pass untouched request through to the next request handler. */
export class NoopInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler):
    Observable<HttpEvent<any>> {
    return next.handle(req);

The intercept method transforms a request into an Observable that eventually returns the HTTP response. In this sense, each interceptor is fully capable of handling the request entirely by itself.

Most interceptors inspect the request on the way in and forward the potentially altered request to the handle() method of the next object which implements the HttpHandler interface.

export abstract class HttpHandler {
  abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;

Like intercept(), the handle() method transforms an HTTP request into an Observable of HttpEvents which ultimately include the server's response. The intercept() method could inspect that observable and alter it before returning it to the caller.

This no-op interceptor calls next.handle() with the original request and returns the observable without doing a thing.

The next object

The next object represents the next interceptor in the chain of interceptors. The final next in the chain is the HttpClient backend handler that sends the request to the server and receives the server's response.

Most interceptors call next.handle() so that the request flows through to the next interceptor and, eventually, the backend handler. An interceptor could skip calling next.handle(), short-circuit the chain, and return its own Observable with an artificial server response.

This is a common middleware pattern found in frameworks such as Express.js.

Provide the interceptor

The NoopInterceptor is a service managed by Angular's dependency injection (DI) system. Like other services, you must provide the interceptor class before the app can use it.

Because interceptors are optional dependencies of the HttpClient service, you must provide them in the same injector or a parent of the injector that provides HttpClient. Interceptors provided after DI creates the HttpClient are ignored.

This app provides HttpClient in the app's root injector, as a side-effect of importing the HttpClientModule in AppModule. You should provide interceptors in AppModule as well.

After importing the HTTP_INTERCEPTORS injection token from @angular/common/http, write the NoopInterceptor provider like this:

{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },

Notice the multi: true option. This required setting tells Angular that HTTP_INTERCEPTORS is a token for a multiprovider that injects an array of values, rather than a single value.

You could add this provider directly to the providers array of the AppModule. However, it's rather verbose and there's a good chance that you'll create more interceptors and provide them in the same way. You must also pay close attention to the order in which you provide these interceptors.

Consider creating a "barrel" file that gathers all the interceptor providers into an httpInterceptorProviders array, starting with this first one, the NoopInterceptor.

/* "Barrel" of Http Interceptors */
import { HTTP_INTERCEPTORS } from '@angular/common/http';

import { NoopInterceptor } from './noop-interceptor';

/** Http interceptor providers in outside-in order */
export const httpInterceptorProviders = [
  { provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },

Then import and add it to the AppModule providers array like this:

providers: [

As you create new interceptors, add them to the httpInterceptorProviders array and you won't have to revisit the AppModule.

There are many more interceptors in the complete sample code.

Interceptor order

Angular applies interceptors in the order that you provide them. For example, consider a situation in which you want to handle the authentication of your HTTP requests and log them before sending them to a server. To accomplish this task, you could provide an AuthInterceptor service and then a LoggingInterceptor service. Outgoing requests would flow from the AuthInterceptor to the LoggingInterceptor. Responses from these requests would flow in the other direction, from LoggingInterceptor back to AuthInterceptor. The following is a visual representation of the process:

The last interceptor in the process is always the HttpBackend that handles communication with the server.

You cannot change the order or remove interceptors later. If you need to enable and disable an interceptor dynamically, you'll have to build that capability into the interceptor itself.

Handle interceptor events

Most HttpClient methods return observables of HttpResponse<any>. The HttpResponse class itself is actually an event, whose type is HttpEventType.Response. A single HTTP request can, however, generate multiple events of other types, including upload and download progress events. The methods HttpInterceptor.intercept() and HttpHandler.handle() return observables of HttpEvent<any>.

Many interceptors are only concerned with the outgoing request and return the event stream from next.handle() without modifying it. Some interceptors, however, need to examine and modify the response from next.handle(); these operations can see all of these events in the stream.

Although interceptors are capable of modifying requests and responses, the HttpRequest and HttpResponse instance properties are readonly, rendering them largely immutable. They are immutable for a good reason: An app might retry a request several times before it succeeds, which means that the interceptor chain can re-process the same request multiple times. If an interceptor could modify the original request object, the re-tried operation would start from the modified request rather than the original. Immutability ensures that interceptors see the same request for each try.

Your interceptor should return every event without modification unless it has a compelling reason to do otherwise.

TypeScript prevents you from setting HttpRequest read-only properties.

// Typescript disallows the following assignment because req.url is readonly
req.url = req.url.replace('http://', 'https://');

If you must alter a request, clone it first and modify the clone before passing it to next.handle(). You can clone and modify the request in a single step, as shown in the following example.

// clone request and replace 'http://' with 'https://' at the same time
const secureReq = req.clone({
  url: req.url.replace('http://', 'https://')
// send the cloned, "secure" request to the next handler.
return next.handle(secureReq);

The clone() method's hash argument lets you mutate specific properties of the request while copying the others.

Modify a request body

The readonly assignment guard can't prevent deep updates and, in particular, it can't prevent you from modifying a property of a request body object.

req.body.name = req.body.name.trim(); // bad idea!

If you must modify the request body, follow these steps.

  1. Copy the body and make your change in the copy.
  2. Clone the request object, using its clone() method.
  3. Replace the clone's body with the modified copy.
// copy the body and trim whitespace from the name property
const newBody = { ...body, name: body.name.trim() };
// clone request and set its body
const newReq = req.clone({ body: newBody });
// send the cloned request to the next handler.
return next.handle(newReq);

Clear the request body in a clone

Sometimes you need to clear the request body rather than replace it. To do this, set the cloned request body to null.

TIP: If you set the cloned request body to undefined, Angular assumes you intend to leave the body as is.

newReq = req.clone({ … }); // body not mentioned => preserve original body
newReq = req.clone({ body: undefined }); // preserve original body
newReq = req.clone({ body: null }); // clear the body
Last reviewed on Thu Mar 16 2023

© 2010–2023 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.