Using HTTP Interceptors in Angular

This comprehensive post explains how to use HTTP interceptors in Angular. It includes only text, headings, and code — no icons or images. The post covers setup, configuration, real-world use cases, error handling, authentication, logging, request modification, chaining interceptors, testing, and best practices.

Table of Contents

  1. Introduction to HTTP Interceptors
  2. Why Use Interceptors in Angular
  3. Setting Up HttpClientModule
  4. Creating a Basic Interceptor
  5. Registering an Interceptor
  6. Modifying Requests
  7. Adding Authentication Tokens
  8. Global Error Handling
  9. Logging Requests and Responses
  10. Handling Loading Indicators
  11. Retrying Failed Requests
  12. Response Transformation
  13. Multiple Interceptors and Order of Execution
  14. Conditionally Applying Interceptors
  15. Excluding Specific Requests
  16. Using Dependency Injection in Interceptors
  17. Performance Considerations
  18. Testing HTTP Interceptors
  19. Real-World Examples
  20. Best Practices and Summary

1. Introduction to HTTP Interceptors

HTTP interceptors in Angular provide a powerful mechanism to intercept and modify HTTP requests or responses before they reach the server or the application logic. They are part of Angular’s HttpClient module and are useful for tasks such as authentication, logging, error handling, and caching.


2. Why Use Interceptors in Angular

  • Automatically attach authentication tokens
  • Centralize error handling
  • Log HTTP traffic globally
  • Manage request headers
  • Transform or cache responses
  • Retry failed requests
  • Control loading indicators for network calls

Interceptors act as middleware between your app and external APIs.


3. Setting Up HttpClientModule

Before using interceptors, ensure HttpClientModule is imported in your root or feature module.

import { NgModule } from ‘@angular/core’;

import { BrowserModule } from ‘@angular/platform-browser’;

import { HttpClientModule, HTTP_INTERCEPTORS } from ‘@angular/common/http’;

import { AppComponent } from ‘./app.component’;

@NgModule({

declarations: [AppComponent],

imports: [BrowserModule, HttpClientModule],

bootstrap: [AppComponent]

})

export class AppModule {}


4. Creating a Basic Interceptor

The simplest interceptor logs each outgoing request and incoming response.

import { Injectable } from ‘@angular/core’;

import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from ‘@angular/common/http’;

import { Observable, tap } from ‘rxjs’;

@Injectable()

export class LoggingInterceptor implements HttpInterceptor {

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

console.log(‘Request:’, req.url);

return next.handle(req).pipe(

tap(event => console.log(‘Response event:’, event))

);

}

}


5. Registering an Interceptor

Interceptors are provided globally through the HTTP_INTERCEPTORS multi-provider token.

import { HTTP_INTERCEPTORS } from ‘@angular/common/http’;

import { LoggingInterceptor } from ‘./interceptors/logging.interceptor’;

@NgModule({

providers: [

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

]

})

export class AppModule {}

The multi: true flag allows multiple interceptors to be active simultaneously.


6. Modifying Requests

You can clone and modify requests because HttpRequest objects are immutable.

@Injectable()

export class HeaderInterceptor implements HttpInterceptor {

intercept(req: HttpRequest<any>, next: HttpHandler) {

const cloned = req.clone({ setHeaders: { ‘X-Custom-Header’: ‘AngularApp’ } });

return next.handle(cloned);

}

}


7. Adding Authentication Tokens

A common use of interceptors is attaching JWT or Bearer tokens to all outgoing requests.

@Injectable()

export class AuthInterceptor implements HttpInterceptor {

intercept(req: HttpRequest<any>, next: HttpHandler) {

const token = localStorage.getItem(‘auth_token’);

if (token) {

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

return next.handle(cloned);

}

return next.handle(req);

}

}

You can extend this to refresh tokens automatically when expired.


8. Global Error Handling

Intercept all HTTP errors to log them or display global notifications.

import { catchError } from ‘rxjs/operators’;

import { throwError } from ‘rxjs’;

@Injectable()

export class ErrorInterceptor implements HttpInterceptor {

intercept(req: HttpRequest<any>, next: HttpHandler) {

return next.handle(req).pipe(

catchError(err => {

console.error(‘HTTP Error:’, err);

alert(Error: ${err.status} - ${err.message});

return throwError(() => err);

})

);

}

}


9. Logging Requests and Responses

Centralize network logging for debugging.

@Injectable()

export class NetworkLoggerInterceptor implements HttpInterceptor {

intercept(req: HttpRequest<any>, next: HttpHandler) {

const start = Date.now();

return next.handle(req).pipe(

tap({

next: () => console.log(Request to ${req.url} took ${Date.now() - start}ms),

error: (err) => console.warn(Error on ${req.url}:, err)

})

);

}

}


10. Handling Loading Indicators

Show and hide loading spinners globally.

@Injectable()

export class LoadingInterceptor implements HttpInterceptor {

private requests: HttpRequest<any>[] = [];

removeRequest(req: HttpRequest<any>) {

const i = this.requests.indexOf(req);

if (i >= 0) {

this.requests.splice(i, 1);

}

this.isLoading = this.requests.length > 0;

}

isLoading = false;

intercept(req: HttpRequest<any>, next: HttpHandler) {

this.requests.push(req);

this.isLoading = true;

return next.handle(req).pipe(

finalize(() => this.removeRequest(req))

);

}

}

Bind isLoading to your spinner component.


11. Retrying Failed Requests

Use interceptors for retry logic on transient errors.

import { retry, catchError } from ‘rxjs/operators’;

@Injectable()

export class RetryInterceptor implements HttpInterceptor {

intercept(req: HttpRequest<any>, next: HttpHandler) {

return next.handle(req).pipe(

retry(3),

catchError(err => throwError(() => err))

);

}

}


12. Response Transformation

You can transform server responses before components receive them.

import { map } from ‘rxjs/operators’;

@Injectable()

export class TransformInterceptor implements HttpInterceptor {

intercept(req: HttpRequest<any>, next: HttpHandler) {

return next.handle(req).pipe(

map(event => {

// modify response if needed

return event;

})

);

}

}


13. Multiple Interceptors and Order of Execution

Interceptors run in the order they are provided and return responses in reverse order.

providers: [

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

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

]

In this case, AuthInterceptor executes before LoggingInterceptor for outgoing requests, but LoggingInterceptor handles responses first.


14. Conditionally Applying Interceptors

Sometimes you may only want to apply interceptors for specific domains or endpoints.

if (req.url.startsWith(‘https://api.example.com’)) {

const cloned = req.clone({ setHeaders: { ‘X-Special’: ‘Yes’ } });

return next.handle(cloned);

}

return next.handle(req);


15. Excluding Specific Requests

You can exclude requests such as authentication or static assets.

if (req.url.includes(‘/auth/login’)) {

return next.handle(req);

}

This prevents adding headers or logging for those requests.


16. Using Dependency Injection in Interceptors

Interceptors can inject services for advanced logic.

constructor(private authService: AuthService, private logger: LoggerService) {}

intercept(req: HttpRequest<any>, next: HttpHandler) {

const token = this.authService.getToken();

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

this.logger.log(‘Authenticated request sent’);

return next.handle(cloned);

}


17. Performance Considerations

  • Keep interceptor logic lightweight.
  • Avoid heavy computation or blocking operations.
  • Use conditionals to limit execution to relevant URLs.
  • Chain interceptors carefully to avoid infinite loops.

18. Testing HTTP Interceptors

Test interceptors using HttpClientTestingModule and HttpTestingController.

import { TestBed } from ‘@angular/core/testing’;

import { HTTP_INTERCEPTORS, HttpClientTestingModule, HttpTestingController } from ‘@angular/common/http/testing’;

import { AuthInterceptor } from ‘./auth.interceptor’;

import { HttpClient } from ‘@angular/common/http’;

describe(‘AuthInterceptor’, () => {

let httpMock: HttpTestingController;

let httpClient: HttpClient;

beforeEach(() => {

TestBed.configureTestingModule({

imports: [HttpClientTestingModule],

providers: [

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

]

});

httpMock = TestBed.inject(HttpTestingController);

httpClient = TestBed.inject(HttpClient);

});

it(‘should add Authorization header’, () => {

localStorage.setItem(‘auth_token’, ‘abc123’);

httpClient.get(‘/test’).subscribe();

const req = httpMock.expectOne(‘/test’);

expect(req.request.headers.has(‘Authorization’)).toBeTrue();

});

});


19. Real-World Examples

Global Error Logging + Auth Header Combination

providers: [

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

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

]

This ensures tokens are added first and errors are caught globally.

Refresh Token Flow

if (err.status === 401) {

return this.authService.refreshToken().pipe(

switchMap(() => next.handle(this.addToken(req)))

);

}

Cache Interceptor Example


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *