Hone logo
Hone
Problems

Angular HTTP Interceptor Chain Builder

This challenge focuses on building a flexible and reusable system for managing and executing multiple Angular HTTP interceptors in a specific order. Understanding how to chain interceptors is crucial for implementing cross-cutting concerns like authentication, logging, error handling, and request/response transformation in Angular applications.

Problem Description

Your task is to create a service in Angular that allows developers to register multiple HTTP interceptors and then provides a method to execute them sequentially. This service will act as a central point for managing the interceptor chain, ensuring that each interceptor has a chance to modify requests and responses before they are finally handled by the HttpClient.

Key Requirements:

  1. Interceptor Registration: The service should allow the registration of multiple interceptor functions. Each interceptor function will receive the request and a next function as arguments.
  2. Sequential Execution: When a request is processed, the registered interceptors must be executed in the order they were registered.
  3. next Function Handling: Each interceptor must call the next function to pass control to the subsequent interceptor in the chain, or to the final HttpClient handler if it's the last interceptor.
  4. Request/Response Modification: Interceptors should be able to modify the outgoing request (e.g., add headers) and the incoming response (e.g., transform data).
  5. Error Propagation: Errors thrown by any interceptor or the HttpClient should propagate correctly through the chain.
  6. Return Type: The service's execution method should return an observable that emits the final processed response or an error.

Expected Behavior:

When a request is made, the service will iterate through its registered interceptors. The first interceptor will receive the request and a next function that, when called, will invoke the second interceptor (or the HttpClient if no more interceptors). This continues until the last interceptor, which calls the HttpClient directly. The response from the HttpClient is then passed back up the chain, allowing each interceptor to potentially modify or handle it before it's returned to the original caller.

Edge Cases:

  • No Interceptors Registered: The service should gracefully handle cases where no interceptors are registered, passing the request directly to the HttpClient.
  • Interceptor Throws an Error: If an interceptor throws an error, the chain should be broken, and the error should be observable.
  • Interceptor Returns a Different Observable: An interceptor might choose to short-circuit the request and return its own observable (e.g., from a cached response).

Examples

Example 1:

// Assume a hypothetical HttpClient and a service called InterceptorService
// You will need to implement InterceptorService

// Interceptor definitions
const authInterceptor = (req: HttpRequest<any>, next: HttpHandler) => {
  console.log('Auth Interceptor: Adding Authorization Header');
  const authReq = req.clone({ setHeaders: { Authorization: 'Bearer my-token' } });
  return next.handle(authReq);
};

const loggingInterceptor = (req: HttpRequest<any>, next: HttpHandler) => {
  console.log('Logging Interceptor: Request started');
  return next.handle(req).pipe(
    tap(
      event => console.log('Logging Interceptor: Response received', event),
      error => console.error('Logging Interceptor: Error occurred', error)
    )
  );
};

// --- InterceptorService Usage ---
// Initialize InterceptorService and register interceptors
const interceptorService = new InterceptorService();
interceptorService.registerInterceptor(authInterceptor);
interceptorService.registerInterceptor(loggingInterceptor);

// Hypothetical HttpClient (simplified for demonstration)
class MockHttpClient {
  handle(req: HttpRequest<any>): Observable<HttpResponse<any>> {
    console.log(`HttpClient: Handling request for ${req.url}`);
    // Simulate a successful response
    return of(new HttpResponse({ status: 200, body: { message: 'Data fetched' } }));
  }
}

const mockHttpClient = new MockHttpClient();

// Execute the interceptor chain
const request = new HttpRequest('GET', '/api/data');
const finalObservable = interceptorService.executeChain(request, mockHttpClient);

finalObservable.subscribe({
  next: response => console.log('Final Subscriber: Received response', response),
  error: err => console.error('Final Subscriber: Error', err)
});

/* Expected Output:
Auth Interceptor: Adding Authorization Header
HttpClient: Handling request for /api/data
Logging Interceptor: Request started
Logging Interceptor: Response received HttpResponse { ... }
Final Subscriber: Received response { message: 'Data fetched' }
*/

Example 2:

// --- InterceptorService Usage with error ---

// Interceptor definition that throws an error
const errorProneInterceptor = (req: HttpRequest<any>, next: HttpHandler) => {
  console.log('Error Prone Interceptor: This will throw');
  throw new Error('Something went wrong in errorProneInterceptor');
  // return next.handle(req); // This line will not be reached
};

// --- InterceptorService Usage ---
const interceptorService = new InterceptorService();
interceptorService.registerInterceptor(authInterceptor); // Using from Example 1
interceptorService.registerInterceptor(errorProneInterceptor);
interceptorService.registerInterceptor(loggingInterceptor); // This will not be reached

const mockHttpClient = new MockHttpClient(); // Using from Example 1

const request = new HttpRequest('GET', '/api/protected');
const finalObservable = interceptorService.executeChain(request, mockHttpClient);

finalObservable.subscribe({
  next: response => console.log('Final Subscriber: Received response', response),
  error: err => console.error('Final Subscriber: Error', err)
});

/* Expected Output:
Auth Interceptor: Adding Authorization Header
Error Prone Interceptor: This will throw
Final Subscriber: Error Error: Something went wrong in errorProneInterceptor
*/

Example 3: No Interceptors Registered

// --- InterceptorService Usage ---
const interceptorService = new InterceptorService();
// No interceptors registered

const mockHttpClient = new MockHttpClient(); // Using from Example 1

const request = new HttpRequest('GET', '/api/simple');
const finalObservable = interceptorService.executeChain(request, mockHttpClient);

finalObservable.subscribe({
  next: response => console.log('Final Subscriber: Received response', response),
  error: err => console.error('Final Subscriber: Error', err)
});

/* Expected Output:
HttpClient: Handling request for /api/simple
Final Subscriber: Received response HttpResponse { ... }
*/

Constraints

  • The InterceptorService should be implemented as a TypeScript class.
  • Each interceptor function signature should be (req: HttpRequest<any>, next: HttpHandler) => Observable<HttpEvent<any>>.
  • The HttpHandler interface from @angular/common/http should be used.
  • The HttpRequest and HttpResponse classes from @angular/common/http should be used.
  • The service should handle up to 100 registered interceptors without significant performance degradation.
  • The input HttpRequest object is immutable; interceptors must use .clone() to create modified requests.

Notes

  • This challenge is designed to simulate the core logic of Angular's HttpInterceptor mechanism. You will not be integrating with the actual Angular HttpClient module directly in your solution but rather mimicking its behavior for the purpose of this challenge.
  • You'll need to define mock HttpRequest, HttpResponse, and HttpHandler types if you're not working within an Angular project. For this exercise, you can assume these types are available or create simplified versions.
  • Consider using RxJS operators like tap and catchError within your interceptor functions to handle side effects and errors.
  • The next function in your interceptor signature is a crucial part of the chain. Ensure it's called correctly to pass control.
Loading editor...
typescript