Angular Interceptors Unleashed: Solving Complex Scenarios with Ease. Practical Use Cases and Examples (2023)
Tags: javascript, angular, programming, typescript
Published: 2023-12-05 17:17:03

Angular is a powerful and popular JavaScript framework used for building dynamic web applications. One of its key features is the HttpClient module, which provides an easy way to make HTTP requests to external APIs or your server. Angular interceptors are a crucial part of this module, allowing you to intercept and manipulate HTTP requests and responses. In this article, we’ll explore the most common use cases for Angular interceptors and provide code examples to help you get started.

**What Are Angular Interceptors?**
Angular interceptors are middleware-like services that can be used to intercept and modify HTTP requests and responses globally within your application. They are useful for implementing cross-cutting concerns such as authentication, error handling, logging, and caching.

Interceptors work by defining a series of functions that can be executed before or after an HTTP request is sent or a response is received. These functions are organized into two main categories:

1. **Request Interceptors:** These functions are executed before the HTTP request is sent. You can use them to modify the request headers, add authentication tokens, or perform any other necessary pre-processing.
2. **Response Interceptors:** These functions are executed after the HTTP response is received but before it is passed to the calling code. They allow you to inspect and modify the response, handle errors, or perform post-processing.
Now, let’s dive into some common use cases for Angular interceptors and provide code examples for each.

**Common Use Cases for Angular Interceptors**
**1. Authentication Interceptor**
An authentication interceptor is used to add authentication tokens to outgoing requests and handle authentication-related errors. This is essential for securing your application’s API requests.

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

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler) {
    const authToken = 'your-auth-token';
    const authRequest = request.clone({
      headers: request.headers.set('Authorization', `Bearer ${authToken}`),
    });
    return next.handle(authRequest);
  }
}
```
**2. Error Handling Interceptor**
An error handling interceptor can be used to centralize error handling for HTTP requests. It can capture HTTP errors, log them, and perform appropriate actions like displaying error messages.

```
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
  HttpErrorResponse,
} from '@angular/common/http';
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler) {
    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        // Handle and log the error here
        console.error('HTTP Error:', error);
        // Optionally rethrow the error to propagate it
        return throwError(error);
      })
    );
  }
}
```

**3. Logging Interceptor**
A logging interceptor can be used to log the details of HTTP requests and responses, which is helpful for debugging and monitoring.

```
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
  HttpResponse,
} from '@angular/common/http';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler) {
    return next.handle(request).pipe(
      tap((event) => {
        if (event instanceof HttpResponse) {
          console.log('HTTP Response:', event);
        }
      })
    );
  }
}
```

**4. Caching Interceptor**
A caching interceptor can be used to implement client-side caching for HTTP responses, reducing the number of unnecessary network requests.

```
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
  HttpResponse,
  HttpHeaders,
} from '@angular/common/http';
import { tap } from 'rxjs/operators';

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

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

    const cachedResponse = this.cache.get(request.url);

    if (cachedResponse) {
      return of(cachedResponse);
    }

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

**5. Headers Interceptor**
A headers interceptor can be used to add custom headers to outgoing HTTP requests. This is often used to set headers like ‘Content-Type’ or include API keys.

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

@Injectable()
export class HeadersInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler) {
    const headers = new HttpHeaders({
      'Content-Type': 'application/json',
      'X-API-Key': 'your-api-key',
    });
    const headersRequest = request.clone({ headers });
    return next.handle(headersRequest);
  }
}
```

**6. Loading Indicator Interceptor**
A loading indicator interceptor can be used to show and hide loading spinners or progress bars during HTTP requests, providing a better user experience.

```
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
} from '@angular/common/http';
import { LoadingService } from './loading.service';
import { finalize } from 'rxjs/operators';

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

  intercept(request: HttpRequest<any>, next: HttpHandler) {
    this.loadingService.showLoading();
    return next.handle(request).pipe(
      finalize(() => {
        this.loadingService.hideLoading();
      })
    );
  }
}
```

**7. Timeout Interceptor**
A timeout interceptor can be used to set a maximum timeout for HTTP requests. It can be useful to prevent long-running requests from blocking your application.

```
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
} from '@angular/common/http';
import { throwError, timer, Observable } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';

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

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
    const timeoutDuration = 10000; // 10 seconds
    return next.handle(request).pipe(
      timeout(timeoutDuration),
      catchError((error) => {
        if (error.name === 'TimeoutError') {
          // Handle timeout error here
          console.error('Request timed out:', request.url);
          return throwError('Request timed out');
        }
        return throwError(error);
      })
    );
  }
}
```

**8. Base URL Interceptor**
A base URL interceptor can be used to prepend a base URL to all HTTP requests, simplifying the configuration of API endpoints.

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

@Injectable()
export class BaseUrlInterceptor implements HttpInterceptor {
  private baseUrl = 'https://api.example.com';

  intercept(request: HttpRequest<any>, next: HttpHandler) {
    const apiRequest = request.clone({
      url: `${this.baseUrl}${request.url}`,
    });
    return next.handle(apiRequest);
  }
}
```

**9. Retry Interceptor**
A retry interceptor can be used to automatically retry failed HTTP requests, which can be helpful in handling intermittent network issues.

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

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

  intercept(request: HttpRequest<any>, next: HttpHandler) {
    // Define the maximum number of retries
    const maxRetries = 3;
    return next.handle(request).pipe(retry(maxRetries));
  }
}
```
**10. Offline Mode Interceptor**
An offline mode interceptor can be used to detect when the user’s device is offline and handle HTTP requests accordingly, such as storing them for later or showing a friendly message.

```
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
  HttpErrorResponse,
} from '@angular/common/http';
import { throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

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

  intercept(request: HttpRequest<any>, next: HttpHandler) {
    // Check if the device is offline
    if (!navigator.onLine) {
      // Handle offline mode (e.g., store requests for later)
      console.error('Device is offline. Request not sent:', request.url);
      return throwError(new HttpErrorResponse({ status: 0, statusText: 'Offline' }));
    }

    return next.handle(request);
  }
}
```

**11. JWT Refresh Token Interceptor**
A JWT refresh token interceptor can be used to automatically refresh expired JSON Web Tokens (JWTs) and seamlessly continue making authenticated requests.

```
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
  HttpErrorResponse,
} from '@angular/common/http';
import { AuthService } from './auth.service';
import { catchError, switchMap } from 'rxjs/operators';
import { throwError } from 'rxjs';

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

  intercept(request: HttpRequest<any>, next: HttpHandler) {
    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.status === 401 && error.error && error.error.message === 'Token expired') {
          // Token expired; attempt to refresh it
          return this.authService.refreshToken().pipe(
            switchMap(() => {
              // Retry the original request with the new token
              const updatedRequest = request.clone({
                setHeaders: {
                  Authorization: `Bearer ${this.authService.getAccessToken()}`,
                },
              });
              return next.handle(updatedRequest);
            }),
            catchError(() => {
              // Refresh token failed; log out the user or handle the error
              // For example, you can redirect to the login page
              this.authService.logout();
              return throwError('Token refresh failed');
            })
          );
        }
        return throwError(error);
      })
    );
  }
}
```

**12. Request Timing Interceptor**
A request timing interceptor can be used to measure and log the time taken for each HTTP request, helping you identify performance bottlenecks.

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

@Injectable()
export class RequestTimingInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler) {
    const startTime = Date.now();
    return next.handle(request).pipe(
      tap(() => {
        const endTime = Date.now();
        const duration = endTime - startTime;
        console.log(`Request to ${request.url} took ${duration}ms`);
      })
    );
  }
}
```

**13. Localization Interceptor**
A localization interceptor can be used to automatically include the user’s preferred language or locale in HTTP requests, ensuring that the server sends responses in the appropriate language.

```
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
} from '@angular/common/http';
import { LocaleService } from './locale.service';

@Injectable()
export class LocalizationInterceptor implements HttpInterceptor {
  constructor(private localeService: LocaleService) {}

  intercept(request: HttpRequest<any>, next: HttpHandler) {
    const userLocale = this.localeService.getUserLocale();
    const localizedRequest = request.clone({
      setHeaders: {
        'Accept-Language': userLocale,
      },
    });
    return next.handle(localizedRequest);
  }
}
```

**14. Content Security Policy (CSP) Interceptor**
A CSP interceptor can be used to automatically add Content Security Policy headers to outgoing HTTP requests to improve security.

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

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

  intercept(request: HttpRequest<any>, next: HttpHandler) {
    const cspHeader = "default-src 'self'; script-src 'self' 'unsafe-inline'";
    const cspRequest = request.clone({
      setHeaders: {
        'Content-Security-Policy': cspHeader,
      },
    });
    return next.handle(cspRequest);
  }
}
```

**15. Compression Interceptor**
A compression interceptor can be used to automatically request compressed content (e.g., gzip) from the server, reducing the amount of data transferred over the network.

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

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

  intercept(request: HttpRequest<any>, next: HttpHandler) {
    const compressedRequest = request.clone({
      setHeaders: {
        'Accept-Encoding': 'gzip, deflate',
      },
    });
    return next.handle(compressedRequest);
  }
}
```
**Conclusion**
Angular interceptors are a powerful tool for handling common HTTP-related tasks in your application. They allow you to centralize and modularize concerns such as authentication, error handling, logging, and caching. By implementing interceptors, you can improve the maintainability and scalability of your Angular applications while keeping your code clean and organized.