Angular’s 17 Interceptors Complete Tutorial
Tags: angular, angular17, frontend
Published: 2024-05-08 06:14:47

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);
    })
  );
}
```

## Configuring Functional Interceptors

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

```
bootstrapApplication(AppComponent, {providers: [
  provideHttpClient(
    withInterceptors([loggingInterceptor, cachingInterceptor]),
  )
]});
```
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);
    }
  }));
}
```

## 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);
      })
    );
  }
}
```
**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},
]});
```

## 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
```
Run the following command to generate a new service:

```
ng generate service auth
```
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';
  }
}
```
Run the following command to generate a new service:

```
ng generate service loading
```
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;
  }
}
```
Run the following command to generate a new interceptor:

```
ng generate interceptor functional
```
Now, let’s modify the **functional.intercepor.ts** file in the **src/ap**p 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);
}
```
Run the following command to generate a new interceptor:

```
ng generate interceptor dibased
```
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);
  }
}
```
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 }
  ]
};
```
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>
```
Now, you can run the application using the following command:

```
ng serve
```
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](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](https://bytewave.substack.com/) today, to access our comprehensive guides and tutorials designed to simplify complex topics and guide you through practical applications.