Skip to main content
HTTP interceptors allow you to intercept and transform HTTP requests and responses globally in your Angular application. They’re useful for adding authentication tokens, logging, error handling, caching, and more.

Interface Definition

interface HttpInterceptor {
  intercept(
    req: HttpRequest<any>, 
    next: HttpHandler
  ): Observable<HttpEvent<any>>;
}

Functional Interceptor

Angular also provides a functional approach with HttpInterceptorFn:
type HttpInterceptorFn = (
  req: HttpRequest<unknown>,
  next: HttpHandlerFn
) => Observable<HttpEvent<unknown>>;

Importing

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

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // Interceptor logic
    return next.handle(req);
  }
}

Methods

intercept()

Intercepts an HTTP request and handles it.
req
HttpRequest<any>
required
The outgoing request object to handle
next
HttpHandler | HttpHandlerFn
required
The next interceptor in the chain, or the backend if no interceptors remain
return
Observable<HttpEvent<any>>
An observable of the HTTP event stream

Usage Examples

The functional approach is more lightweight and works with Angular’s injection context:
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const authService = inject(AuthService);
  const token = authService.getToken();
  
  if (token) {
    const cloned = req.clone({
      headers: req.headers.set('Authorization', `Bearer ${token}`)
    });
    return next(cloned);
  }
  
  return next(req);
};

Class-based Interceptor (Legacy)

The traditional class-based approach:
import { Injectable } from '@angular/core';
import { 
  HttpInterceptor, 
  HttpRequest, 
  HttpHandler,
  HttpEvent 
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) {}
  
  intercept(
    req: HttpRequest<any>, 
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    const token = this.authService.getToken();
    
    if (token) {
      const cloned = req.clone({
        headers: req.headers.set('Authorization', `Bearer ${token}`)
      });
      return next.handle(cloned);
    }
    
    return next.handle(req);
  }
}

Providing Interceptors

Functional Interceptors

Use withInterceptors() to provide functional interceptors:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './auth.interceptor';
import { loggingInterceptor } from './logging.interceptor';

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(
      withInterceptors([authInterceptor, loggingInterceptor])
    )
  ]
});

Class-based Interceptors

Use HTTP_INTERCEPTORS token to provide class-based interceptors:
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ]
})
export class AppModule {}
Or with standalone APIs:
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { HTTP_INTERCEPTORS } from '@angular/common/http';

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(withInterceptorsFromDi()),
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ]
});

Interceptor Chain

Interceptors are called in the order they are provided:
provideHttpClient(
  withInterceptors([
    authInterceptor,      // Called first
    loggingInterceptor,   // Called second
    retryInterceptor,     // Called third
    cacheInterceptor      // Called last
  ])
)
Each interceptor must call next() to pass the request to the next interceptor. If an interceptor doesn’t call next(), the request chain is broken and the request won’t reach the server.

Request Transformation

Requests are immutable, so you must clone them to make changes:
export const modifyHeadersInterceptor: HttpInterceptorFn = (req, next) => {
  // Clone the request and modify headers
  const modified = req.clone({
    headers: req.headers
      .set('X-Custom-Header', 'value')
      .set('Accept', 'application/json')
  });
  
  return next(modified);
};

Common Transformations

const cloned = req.clone({
  headers: req.headers.set('X-API-Key', 'abc123')
});

Response Transformation

Transform responses using RxJS operators:
import { map } from 'rxjs/operators';
import { HttpResponse } from '@angular/common/http';

export const transformResponseInterceptor: HttpInterceptorFn = (req, next) => {
  return next(req).pipe(
    map(event => {
      if (event instanceof HttpResponse) {
        // Transform the response body
        return event.clone({
          body: {
            data: event.body,
            timestamp: Date.now()
          }
        });
      }
      return event;
    })
  );
};

Conditional Interception

Apply interception logic conditionally:
export const conditionalInterceptor: HttpInterceptorFn = (req, next) => {
  // Only intercept API calls
  if (req.url.startsWith('/api/')) {
    const modified = req.clone({
      headers: req.headers.set('X-API-Key', 'abc123')
    });
    return next(modified);
  }
  
  // Pass through without modification
  return next(req);
};

Error Handling

Handle errors at the interceptor level:
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
import { Router } from '@angular/router';
import { inject } from '@angular/core';

export const authErrorInterceptor: HttpInterceptorFn = (req, next) => {
  const router = inject(Router);
  
  return next(req).pipe(
    catchError(error => {
      if (error.status === 401) {
        // Redirect to login on unauthorized
        router.navigate(['/login']);
      }
      return throwError(() => error);
    })
  );
};

Best Practices

Prefer functional interceptors (HttpInterceptorFn) over class-based interceptors for better tree-shaking and simpler code.
Each interceptor should have a single responsibility. Create multiple interceptors rather than one complex interceptor.
Always call next() (functional) or next.handle() (class-based) unless you intentionally want to block the request.
Requests are immutable. Always clone the request before modifying it.
Use proper error handling to prevent the interceptor chain from breaking.
Interceptors execute in the order they’re provided. Order matters for operations like authentication and logging.

Common Use Cases

Authentication

Add authentication tokens to outgoing requests

Logging

Log all HTTP requests and responses

Error Handling

Centralized error handling and recovery

Loading States

Show/hide loading indicators

Caching

Cache responses for improved performance

Retry Logic

Automatically retry failed requests

Request Transformation

Modify requests before sending

Response Transformation

Transform responses before consumption

See Also

HTTP Overview

Introduction to Angular’s HTTP client

HttpClient

Complete API reference for HttpClient