DEV Community

Cover image for Angular’s 17 Interceptors Complete Tutorial
bytebantz
bytebantz

Posted on • Edited on

Angular’s 17 Interceptors Complete Tutorial

Angular’s HttpClient offers a powerful feature called interceptors, which act as middleware for HTTP requests and responses. In this guide, we’ll explore everything you need to know about interceptors, from basic concepts to advanced techniques.

Understanding Interceptors

Interceptors in HttpClient are functions or classes that can intercept outgoing HTTP requests and incoming responses. Interceptors act as middleware for HTTP requests and responses.

Interceptors are like helpers for handling HTTP requests and responses in Angular.

Common use cases for interceptors

  • Adding authentication headers
  • Retrying failed requests
  • Caching responses
  • Measuring server response times and log them
  • Driving UI elements such as a loading spinner while network operations are in progress

Types of Interceptors

HttpClient supports two main types of interceptors:

· Functional Interceptors

· DI-based Interceptors

Both types of interceptors have access to the outgoing request and can modify it before it is sent. They can also intercept the response before it reaches the application code.

1. Functional Interceptors:

These are functions that accept the outgoing request and a next function representing the next step in the interceptor chain. They are preferred for their predictable behavior.

Defining a Functional Interceptor
This interceptor logs the outgoing request URL before forwarding it to the next step in the chain and handles errors.

export const loggingInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  console.log('Request URL: ' + req.url);
  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      console.error('Logging Interceptor Functional Error:', error);
      return throwError(()=> error);
    })
  );
}
Enter fullscreen mode Exit fullscreen mode

Configuring Functional Interceptors

Functional Interceptors are configured during HttpClient setup using the withInterceptors feature:

bootstrapApplication(AppComponent, {providers: [
  provideHttpClient(
    withInterceptors([loggingInterceptor, cachingInterceptor]),
  )
]});
Enter fullscreen mode Exit fullscreen mode

Here, we configure both loggingInterceptor and cachingInterceptor. They will form a chain where loggingInterceptor processes the request before cachingInterceptor.

Intercepting Response Events:

Interceptors can transform the stream of HttpEvents returned by next, allowing access to or manipulation of the response.

export const loggingInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  console.log('Request URL: ' + req.url);
  return next(req).pipe(tap(event => {
    if (event.type === HttpEventType.Response) {
      console.log(req.url, 'returned a response with status', event.status);
    }
  }));
}
Enter fullscreen mode Exit fullscreen mode

2. DI-based Interceptors:

These are injectable classes that implement the HttpInterceptor interface. They offer similar capabilities to functional interceptors but are configured differently through Angular’s Dependency Injection system.

Defining a DI-based Interceptor
DI-based interceptors are defined as injectable classes implementing the HttpInterceptor interface.

This interceptor logs the outgoing request URL before forwarding it to the next step in the chain.

@Injectable()
export class LoggingInterceptorDI implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log('Request URL: ' + req.url);
    return next.handle(req).pipe(
      catchError((error: HttpErrorResponse) => {
        console.error('Logging Interceptor DI Error:', error);
        return throwError(()=> error);
      })
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Configuring DI-based Interceptors
DI-based interceptors are configured through Angular’s Dependency Injection system:

bootstrapApplication(AppComponent, {providers: [
  provideHttpClient(
    withInterceptorsFromDi(),
  ),
  {provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true},
]});
Enter fullscreen mode Exit fullscreen mode

Example

Now let’s create a simple Angular project where we’ll implement both functional and DI-based interceptors

This simplified example covers all the functionalities mentioned: adding authentication headers, retrying failed requests, measuring server response times, loading spinner while network operations are in progress and logging.

Run the following command to generate a new project:

ng new interceptors-demo
Enter fullscreen mode Exit fullscreen mode

Run the following command to generate a new service:

ng generate service auth
Enter fullscreen mode Exit fullscreen mode

Now, let’s modify the auth.service.ts file in the src/app directory to manage authentication and retrieve the authentication token

// auth.service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class AuthService {
  getAuthToken(): string {
    // Logic to retrieve authentication token
    return 'your_auth_token_here';
  }
}
Enter fullscreen mode Exit fullscreen mode

Run the following command to generate a new service:

ng generate service loading
Enter fullscreen mode Exit fullscreen mode

Now, let’s modify the loading.service.ts file in the src/app directory to show and hide the loading spinner UI element.

import { Injectable } from '@angular/core';

@Injectable()
export class LoadingService {
  private loading = false;

  showLoadingSpinner() {
    this.loading = true;
    console.log('Loading spinner shown'); // Log when loading spinner is shown
    // Logic to show loading spinner UI element
  }

  hideLoadingSpinner() {
    this.loading = false;
    console.log('Loading spinner hidden'); // Log when loading spinner is hidden
    // Logic to hide loading spinner UI element
  }

  isLoading(): boolean {
    return this.loading;
  }
}
Enter fullscreen mode Exit fullscreen mode

Run the following command to generate a new interceptor:

ng generate interceptor functional
Enter fullscreen mode Exit fullscreen mode

Now, let’s modify the functional.intercepor.ts file in the src/app directory to implement functional interceptors:

import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { throwError } from 'rxjs';
import { catchError, finalize, retry } from 'rxjs/operators';
import { LoadingService } from './loading.service';

// Server Response Time Interceptor
export const responseTimeInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  const startTime = Date.now();
  return next(req).pipe(
    finalize(() => {
      const endTime = Date.now();
      const responseTime = endTime - startTime;
      console.log(`Request to ${req.url} took ${responseTime}ms`);      
    })
  );
}

// Loading Spinner Interceptor
export const loadingSpinnerInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  const loadingService = new LoadingService(); // Instantiate the loading service
  loadingService.showLoadingSpinner(); // Show loading spinner UI element

  return next(req).pipe(
    finalize(() => {
      loadingService.hideLoadingSpinner(); // Hide loading spinner UI element
    })
  );
};

export const authInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  const authToken = 'YOUR_AUTH_TOKEN_HERE';

  // Clone the request and add the authorization header
  const authReq = req.clone({
    setHeaders: {
      Authorization: `Bearer ${authToken}`
    }
  });

  // Pass the cloned request with the updated header to the next handler
  return next(authReq);
};

export const retryInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  const maxRetries = 3;

  return next(req).pipe(
    retry(maxRetries),
    catchError((error: HttpErrorResponse) => {
      console.error('Retry Interceptor Functional Error:', error);
      return throwError(()=> error);
    })
  );
};


export const loggingInterceptorFunctional: HttpInterceptorFn = (req, next) => {
  console.log('Request URL: ' + req.url);
  return next(req);
}
Enter fullscreen mode Exit fullscreen mode

Run the following command to generate a new interceptor:

ng generate interceptor dibased
Enter fullscreen mode Exit fullscreen mode

Now, let’s modify the dibased.intercepor.ts file in the src/app directory to implement DI-based interceptors:

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpInterceptor, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, finalize, retry, tap } from 'rxjs/operators';
import { LoadingService } from './loading.service';
import { AuthService } from './auth.service';

@Injectable()
export class ResponseTimeInterceptorDI implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const startTime = Date.now();
    return next.handle(req).pipe(
      tap(event => {
        if (event instanceof HttpResponse) {
          const endTime = Date.now();
          const responseTime = endTime - startTime;
          console.log(`Request to ${req.url} took ${responseTime}ms`);
        }
      })
    );
  }
}

@Injectable()
export class LoadingSpinnerInterceptorDI implements HttpInterceptor {
  constructor(private loadingService: LoadingService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    this.loadingService.showLoadingSpinner(); // Show loading spinner UI element here
    return next.handle(req).pipe(
      finalize(() => {
        this.loadingService.hideLoadingSpinner(); // Hide loading spinner UI element
      })
    );
  }
}

@Injectable()
export class RetryInterceptorDI implements HttpInterceptor {
  constructor() {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const maxRetries = 3; // Customize max retry attempts here
    return next.handle(req).pipe(
      retry(maxRetries),
      catchError((error: HttpErrorResponse) => {
        console.error('Retry Interceptor DI Error:', error);
        return throwError(()=> error);
      })
    );
  }
}

@Injectable()
export class AuthInterceptorDI implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authToken = this.authService.getAuthToken();
    const authReq = req.clone({
      headers: req.headers.set('Authorization', `Bearer ${authToken}`)
    });
    return next.handle(authReq);
  }
}

@Injectable()
export class LoggingInterceptorDI implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log('Request URL: ' + req.url);
    return next.handle(req);
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, we’ll configure both interceptors in the app.config.ts

import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptors, withInterceptorsFromDi } from '@angular/common/http';
import { authInterceptorFunctional, loadingSpinnerInterceptorFunctional, loggingInterceptorFunctional, responseTimeInterceptorFunctional, retryInterceptorFunctional } from './functional.interceptor';
import { AuthInterceptorDI, LoadingSpinnerInterceptorDI, LoggingInterceptorDI, ResponseTimeInterceptorDI, RetryInterceptorDI } from './dibased.interceptor';
import { LoadingService } from './loading.service';
import { AuthService } from './auth.service';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(
      withInterceptors([
        responseTimeInterceptorFunctional,
        loadingSpinnerInterceptorFunctional, 
        authInterceptorFunctional, 
        retryInterceptorFunctional, 
        loggingInterceptorFunctional, 
      ]),
      /* THE COMMENTED CONFIGURATIONS ARE FOR DI-based INTERCEPTORS 
          Comment withInterceptors() and uncomment the below code to use DI-based interceptors
      */

      //withInterceptorsFromDi(),
    ),
    //LoadingService,
    //AuthService,
    // { provide: HTTP_INTERCEPTORS, useClass: ResponseTimeInterceptorDI, multi: true },
    // { provide: HTTP_INTERCEPTORS, useClass: LoadingSpinnerInterceptorDI, multi: true },
    // { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorDI, multi: true },
    // { provide: HTTP_INTERCEPTORS, useClass: RetryInterceptorDI, multi: true },
    // { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptorDI, multi: true }
  ]
};
Enter fullscreen mode Exit fullscreen mode

Now, let’s modify the app.component.ts file to use Angular’s HttpClient to fetch data:

import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HttpClient } from '@angular/common/http';


@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent implements OnInit{
  constructor(private http: HttpClient) { }

  ngOnInit() {
    this.getData();
  }

  getData() {
    this.http.get('https://jsonplaceholder.typicode.com/posts').subscribe({
      next: data => { 
        console.log(data);
      },
      error: error => {
        console.error('Error getting post:', error);
      }
    });
  }
}
Finally, let’s update the app.component.html file to remove the default content:

<div>
  <h1>Welcome to Angular Interceptors Demo</h1>
</div>
Enter fullscreen mode Exit fullscreen mode

Now, you can run the application using the following command:

ng serve
Enter fullscreen mode Exit fullscreen mode

This will start a development server, and you should be able to see the output in the browser console.

Conclusion

Interceptors are powerful tools for managing HTTP traffic within Angular applications. By intercepting requests and responses, developers can implement a wide range of functionalities, from authentication and caching to logging and error handling. Understanding and effectively utilizing interceptors can significantly enhance the reliability, maintainability, security, and performance of Angular applications.

To get the whole code, check the repo below👇👇👇
https://github.com/anthony-kigotho/interceptors-demo

CTA

Many developers and learners encounter tutorials that are either too complex or lacking in detail, making it challenging to absorb new information effectively.

Subscribe to our newsletter today, to access our comprehensive guides and tutorials designed to simplify complex topics and guide you through practical applications.

Top comments (2)

Collapse
 
alejandro_chavez_134071f0 profile image
Alejandro Chavez

¡Gracias, amigo!

Me ayudaste a resolver un error que me tuvo horas dando vueltas.

Collapse
 
jose_piedra_d74fdafe7f517 profile image
jose piedra

thanks, to add if you want to make a retry interceptor only for server errors can use this retry config
retry({
count: MAX_OF_RETRY,
delay: (error: HttpErrorResponse, retryAttempt: number): Observable<number> => {
// if maximum number of retries have been met
// or response is a status code we don't wish to retry, throw error
if (retryAttempt > MAX_OF_RETRY || error.status !== 404) {
return throwError(() => error);
}
console.log(
Attempt ${retryAttempt}: retrying in ${retryAttempt * RETRY_DELAY}ms);
return timer(retryAttempt * RETRY_DELAY);
},
})