Open In App

HTTP Interceptor use-cases in Angular

Last Updated : 19 Sep, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

In Angular, HTTP interceptors are a powerful feature that allows you to intercept and modify HTTP requests and responses at a centralized location. They act as middleware, sitting between the application’s HTTP client (typically the built-in HttpClient module) and the server.

In this article, we will explore some common use cases of HTTP interceptors in Angular.

What Are HTTP Interceptors in Angular?

HTTP Interceptors are a middleware mechanism in Angular’s HttpClient module that intercepts HTTP requests and responses. They allow you to intercept outgoing HTTP requests or incoming HTTP responses and perform operations such as modifying request headers, handling errors, adding authentication tokens, caching responses, and more.

Why Use HTTP Interceptors?

HTTP Interceptors provide a unified way to manage and manipulate HTTP traffic. They can be used to:

  • Add default headers or tokens to every request.
  • Log request and response data for debugging or monitoring.
  • Implement caching to optimize network performance.
  • Modify or handle errors in a centralized manner.

Features of HTTP Interceptor

  • Request Modification: Interceptors can modify outgoing HTTP requests before they are sent to the server. This includes adding headers, transforming request bodies, or even cancelling requests altogether.
  • Response Modification: Interceptors can also modify incoming HTTP responses before they reach the application code. This can involve transforming response data, handling errors, or adding custom logic based on the response.
  • Global Applicability: Interceptors are registered at the module level and are automatically applied to all HTTP requests and responses within the application, ensuring consistent behaviour across different components and services.
  • Chaining: Multiple interceptors can be registered and chained together, allowing for modular and reusable code.
  • Dependency Injection: Interceptors can be injected with other services or dependencies, enabling more complex logic and integration with other parts of the application.

Common Use Cases for HTTP Interceptors

1. Authentication Token Injection

One of the most common uses for HTTP interceptors is injecting authentication tokens (e.g., JWT tokens) into the headers of every outgoing HTTP request. Instead of adding the token manually to every HTTP request, you can centralize this process with an interceptor.

Use Case: Injecting JWT into Headers

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const authToken = localStorage.getItem('authToken'); // Fetch token from storage
const authReq = req.clone({
headers: req.headers.set('Authorization', `Bearer ${authToken}`)
});
return next.handle(authReq);
}
}

This interceptor fetches the JWT token from local storage (or a similar service) and attaches it to the Authorization header of each HTTP request. This eliminates the need for developers to manually attach the token on every service call.

2. Logging Requests and Responses

Logging HTTP requests and responses can help with debugging and performance monitoring. HTTP interceptors are ideal for capturing HTTP traffic for logging purposes.

Use Case: Logging HTTP Traffic

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log('Request URL:', req.url);
console.log('Request Headers:', req.headers);
return next.handle(req).pipe(
tap(event => {
if (event.type === HttpEventType.Response) {
console.log('Response Status:', event.status);
console.log('Response Body:', event.body);
}
})
);
}
}

In this example, the interceptor logs details about both the outgoing request (e.g., URL, headers) and the incoming response (e.g., status, body). This is invaluable for debugging API issues or understanding the flow of data between the client and server.

3. Global Error Handling

Handling errors in every service method can lead to repetitive and error-prone code. Interceptors allow developers to manage HTTP errors in a centralized manner, reducing code duplication.

Use Case: Handling Errors Globally

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError(error => {
if (error.status === 401) {
console.error('Unauthorized request - Redirecting to login');
} else if (error.status === 500) {
console.error('Server error - Please try again later');
}
return throwError(error);
})
);
}
}

This interceptor catches HTTP errors and handles them based on the response status. For example, if the response status is 401, the user can be redirected to the login page, while a 500 error might trigger a user-friendly message.

4. Caching Responses

In some cases, you may want to cache HTTP responses to reduce the number of server requests and improve performance. An interceptor can cache responses and serve them for subsequent requests to the same endpoint.

Use Case: Simple Response Caching

@Injectable()
export class CachingInterceptor implements HttpInterceptor {
private cache = new Map<string, HttpResponse<any>>();

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.method !== 'GET') {
return next.handle(req);
}

const cachedResponse = this.cache.get(req.url);
if (cachedResponse) {
return of(cachedResponse);
}

return next.handle(req).pipe(
tap(event => {
if (event instanceof HttpResponse) {
this.cache.set(req.url, event);
}
})
);
}
}

This caching interceptor stores the HTTP response in memory for GET requests. If the same URL is requested again, the cached response is returned instead of making a new HTTP request to the server. This can greatly improve the performance of applications that frequently request the same data.

5. Modifying Request URLs

Interceptors can also be used to dynamically modify URLs, for instance, to add API versioning or redirect traffic to different base URLs based on the environment (e.g., development vs. production).

Use Case: Modifying Request URLs

@Injectable()
export class UrlInterceptor implements HttpInterceptor {
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const apiReq = req.clone({ url: `https://api.example.com/v1/${req.url}` });
return next.handle(apiReq);
}
}

In this example, the interceptor prepends a base URL and version (v1) to all outgoing requests. This ensures consistency across the application and avoids the need for hardcoding URLs in every service.

Angular application with HTTP Interceptor

Let us create an angular application and use an HTTP interceptor to intercept the requests.

Step 1: Create an angular application

We can make use of the angular cli to create a new application using

ng new http-interceptor-demo
cd http-interceptor-demo

Folder Structure

file
Folder Structure


Dependencies

"dependencies": {
"@angular/animations": "^17.3.0",
"@angular/common": "^17.3.0",
"@angular/compiler": "^17.3.0",
"@angular/core": "^17.3.0",
"@angular/forms": "^17.3.0",
"@angular/platform-browser": "^17.3.0",
"@angular/platform-browser-dynamic": "^17.3.0",
"@angular/platform-server": "^17.3.0",
"@angular/router": "^17.3.0",
"@angular/ssr": "^17.3.7",
"express": "^4.18.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.3"
}

Step 2: Make an HTTP request

It will create a simple http get request using the angular’s HttpClient so that we can intercept it and to use that we will need to import provideHttpClient.

JavaScript
// src/app/app.component.ts

import { Component, OnInit } from '@angular/core';
import {
    HttpClient,
    HttpHeaders,
    HttpErrorResponse,
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, retry, tap } from 'rxjs/operators';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';

@Component({
    selector: 'app-root',
    standalone: true,
    imports: [CommonModule, HttpClientModule],
    template: `
    <div>
        <h1>HTTP Interceptor Use-Cases Demo</h1>
        <button (click)="fetchPosts()">Fetch Posts</button>
        <ul>
        <li *ngFor="let post of posts">{{ post.title }}</li>
      </ul>
      <p *ngIf="errorMessage">{{ errorMessage }}</p>
    </div>
  `,
    styles: [],
})
export class AppComponent implements OnInit {
    posts: any[] = [];
    errorMessage: string = '';

    constructor(private http: HttpClient) { }

    ngOnInit(): void { }

    fetchPosts() {

        const headers = new HttpHeaders({
            Authorization: 'Bearer my-token',
        });

        console.log('Request:', { headers });

        this.http
            .get('https://jsonplaceholder.typicode.com/posts', { headers })
            .pipe(
                retry(3),

                tap((event) => {
                    console.log('Response:', event);
                }),

                catchError((error: HttpErrorResponse) => {
                    console.error('Error:', error);

                    if (error.status === 401) {
                        this.errorMessage = 'Unauthorized access - please log in.';
                    } else if (error.status === 404) {
                        this.errorMessage = 'Resource not found.';
                    } else {
                        this.errorMessage = 'An unknown error occurred.';
                    }

                    return throwError(() => error);
                })
            )
            .subscribe(
                (data: any) => {
                    this.posts = data;
                },
                (error) => {
                    console.error('Subscription error:', error);
                }
            );
    }
}

Step 3: To start the application run the following command

ng serve

Output


Next Article
Article Tags :

Similar Reads