How To Split HTTP Interceptor Between Multiple Backends?

How To Split HTTP Interceptor Between Multiple Backends?

As an Angular developer, have you ever considered applying a different set of HTTP interceptors to another kind of output? Most angular developers during angular application development face this problem, but this blog post will solve that problem quickly.

We will understand the HTTP layer implementation, which will be very helpful. Also, this blog post will teach you how to implement a different type of HttpsClient and module-scoped interceptors of all kinds. This blog will follow a transparent approach to perfect architecture and help you face some cases.

So, our problem is splitting the HTTP interceptor between multiple Angular backends. Let's get to know what it is.

HTTP Interceptors

The HTTP interceptor was first released in Angular 4.3, frequently used for HTTP servers and client apps for communication.

The HTTP interceptor is the layer between the client(browser) API and HttpClient. It allows angular developers to manage or modify HTTP requests. \

How and Where is HttpInterceptor Used?

HttpIntercepter is used in the following ways:

  • Handling HTTP response errors of the entire app

  • Setting headers in HTTP requests

  • Modifying HTTP response

  • Triggering visual side effects

So, our main problem here is setting up headers in HTTP requests against multiple angular backends. Also, we need to add an authorization field to the request header.

The Ideal Approach: Splitting HTTP Interceptor Between Multiple Backends

@Injectable()
export class BasicAuthTokenInterceptor implements HttpInterceptor {
 intercept(request: HttpRequest, next: HttpHandler): Observable<HttpEvent<any>> {
   return next.handle(request.clone({
      setHeaders: {
        Authorization: `Basic ZHN2ZA==`,
      },
    }));
 }
}

@Injectable()
export class BearerAuthTokenInterceptorimplements HttpInterceptor {
 intercept(request: HttpRequest, next: HttpHandler): Observable<HttpEvent<any>> {
   return next.handle(request.clone({
      setHeaders: {
        Authorization: `Bearer 3Hv2ZA==`,
      },
    }));
 }
}

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: BasicAuthTokenInterceptor,
      multi: true,
    },
  ]
})
class FirstFeatureSomeModule() {};

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: BearerAuthTokenInterceptor,
      multi: true,
    }
  ]
})
class SecondFeatureModule() {};

You can see it in the Code above. In that case, we are sending requests to both interceptors, and it needs to be clarified which token type will be set as both use the same header Authorization.

The next possibility of the solution is to combine two interceptors in one and give the interceptor a root-level module. Let's see the Code below.

export class AuthTokenInterceptorimplements HttpInterceptor {
 intercept(request: HttpRequest, next: HttpHandler): Observable<HttpEvent<any>> {
   return next.handle(request.clone({
      setHeaders: {
        Authorization: request.urlWithParams.startsWith(`https://first.feature/api`)
           ? `Basic ZHN2ZA==`,
           :  `Bearer 3Hv2ZA==`,
      },
    }));
 }
}

As you can see, it could be more optimal and look cleaner and more compact. Why? It's because we have two Angular backends.

The Solution is to stick with multiple types of interceptors for feature modules. Also, the dependency injection mechanism from Angular is the Solution.

The Ideal Solution: Splitting HTTP Interceptor Between Multiple Backends

The HttpClient is imported from @nagular/common/http and provided in HttpClientModule.

HttpClient takes a service instance that adds the HttpHandler interface to the constructor:

export abstract class HttpHandler {
 abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
}



// https://github.com/angular/angular/blob/master/packages/common/http/src/client.ts
export declare class HttpClient {
  constructor(handler: HttpHandler);

  // ...
}

Here, the HttpHandler does transform the HttpRequest into a stream of HttpEvent. It will likely be an HttpResponse. We must implement a class for handler inheritance, as HttpClientModule doesn't give us one. So let's name it InterceptingHandler, and it will look like the below:

private chain: HttpHandler;

  constructor(private backend: HttpBackend, private interceptors: HttpInterceptor[]) {
    this.buildChain()
  }

  handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
    return this.chain.handle(req);
  }

  private buildChain(): void {
    this.chain = this.interceptors.reduceRight((next, interceptor) =>
      new InterceptorHandler(next, interceptor), 
      this.backend
    );
  }
}

Here I want to tell you that distinguish between InterceptingHandler and InterceptorHandler. We have to implement Interceptinghandler.

The InterceptingHandler sends requests to the first interceptor, so it passes to the second interceptor and eventually reaches the HttpInterceptor. See the Code below.

 constructor(private next: HttpHandler, private interceptor: HttpInterceptor) {}

   handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
     return this.interceptor.intercept(req, this.next);
   }
}

We will tackle HttpClient once we have InterceptingHandler. The primary Solution is to provide a service in a feature module that takes functionality from HttpClient. Here we will use our custom InterceptingHandler to pass requirements via interceptors.

'An abstraction on feature HttpInterceptor[]'
);

@Injectable()
class FeatureHttpClient extends HttpClient {
   constructor(
      backend: HttpBackend,
      @Inject(HTTP_INTERCEPTORS) interceptors: HttpInterceptor[],
      @Inject(FEATURE_HTTP_INTERCEPTORS) featureInterceptors: HttpInterceptor[],
  ) {
    super(new InterceptingHandler(
      backend,
      [interceptors, featureInterceptors].flat()
    ));
  }
}

Please note how we used HTTP-INTERCEPTORS to get a set of interceptors attached with HttpClient. Why? because of the below points.

  • It got an HttpXsrInterceptor that helps us from malicious exploits of websites by unauthorized commands.

  • Future global tokens can be added or required for each request themselves of the source.

  • A good way to inherit a global interceptor

Final Solution + Code

So here is how we will use a custom HttpClient type in a feature module:

class FeatureApiService {
   constructor( private readonly http: FeatureHttpClient ) {}

   getData(): Observable<any> {
     return this.http.get('...')
   }
}

@NgModule({
  providers: [
    BasicAuthTokenInterceptor,
    {
      provide: FEATURE_HTTP_INTERCEPTORS,
      useClass: BasicAuthTokenInterceptor,
      multi: true,
    },
    FeatureHttpClient,
    FeatureApiService 
  ]
})
class FeatureModule() {};

We should add the FEATURE_HTTP_INTERCEPTORS token for future interceptors, and the HttpIntercepter declaration module is a must.

Final Words

FYI, requests that pass via FeatureHttpCleint are processed by interceptors only defined under the FEATURE_HTTP_INTERCEPTORS token. Custom FeatureHttpCleint will use the standard defined ones. That's it! We finally split HTTP Interceptor between multiple backends.

I hope you grabbed the excellent information from the blog; if you got anything to tell, comment down, or reach our angular developers for any queries.

Thanks for reading.

Did you find this article valuable?

Support Quokka Labs' Blogs by becoming a sponsor. Any amount is appreciated!