Robin
Robin

Reputation: 8518

Separate HttpClient instances per module in Angular

In Angulars HttpInterceptor reference, I've found the following section:

To use the same instance of HttpInterceptors for the entire app, import the HttpClientModule only in your AppModule, and add the interceptors to the root application injector . If you import HttpClientModule multiple times across different modules (for example, in lazy loading modules), each import creates a new copy of the HttpClientModule, which overwrites the interceptors provided in the root module.

Fantastic, this is just what I need. So I can just import in each module the HttpClientModule so my ApiInterceptor which injects a base path and JWT token doesn't interfere with the HttpClient I use in the AppTranslationModule, which gets some translation files.

So I've my AppModule:

@NgModule({
  declarations: [AppComponent],
  imports: [AppRoutingModule, AppTranslationModule, CoreModule],
  bootstrap: [AppComponent]
})
export class AppModule {}

... which imports AppTranslationModule and CoreModule. Each of them import HttpClientModule.

AppTranslationModule:

@Injectable()
export class TranslationLoader implements TranslocoLoader {
  constructor(private http: HttpClient) {}

  getTranslation(lang: string) {
    return this.http.get<Translation>(`/assets/i18n/${lang}.json`);
  }
}

@NgModule({
  imports: [TranslocoModule, HttpClientModule],
  providers: [
    {
      provide: TRANSLOCO_CONFIG,
      useValue: translocoConfig({
        availableLangs: ['en-US', 'de-CH', 'fr-CH', 'it-CH'],
        defaultLang: 'en-US',
        // Remove this option if your application doesn't support changing language in runtime.
        reRenderOnLangChange: true,
        prodMode: environment.production
      })
    },
    { provide: TRANSLOCO_LOADER, useClass: TranslationLoader }
  ],
  exports: []
})
export class AppTranslationModule {}

CoreModule:

@NgModule({
  imports: [BrowserModule, BrowserAnimationsModule, RouterModule, HttpClientModule],
  exports: [DefaultLayoutComponent],
  declarations: [DefaultLayoutComponent],
  providers: [
    { provide: BASE_API_URL, useValue: environment.api },
    { provide: HTTP_INTERCEPTORS, useClass: BaseUrlInterceptor, multi: true }
  ]
})
export class CoreModule {}

Unfortunately the interceptor (BaseUrlInterceptor) from the CoreModule is still applied to the HttpClient in the AppTranslationModule. If I understand the documentation mentioned above correctly, this shouldn't happen? Any ideas what is going on here?

I'm on Angular 11.

Upvotes: 2

Views: 2552

Answers (1)

Robin
Robin

Reputation: 8518

I've found a solution at this blog post. As mentioned there the HttpHandler needs to be changed, to create separate instances of HttpClient which are independent from the other interceptors:

import { HttpBackend, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { InjectionToken, Injector } from '@angular/core';
import { Observable } from 'rxjs';

export class CustomInterceptHandler implements HttpHandler {
  constructor(private next: HttpHandler, private interceptor: HttpInterceptor) {}

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

export class CustomInterceptorHandler implements HttpHandler {
  private chain: HttpHandler | null = null;

  constructor(private backend: HttpBackend, private injector: Injector, private interceptors: InjectionToken<HttpInterceptor[]>) {}

  handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
    if (this.chain === null) {
      const interceptors = this.injector.get(this.interceptors, []);
      this.chain = interceptors.reduceRight((next, interceptor) => new CustomInterceptHandler(next, interceptor), this.backend);
    }

    return this.chain.handle(req);
  }
}

With this the HttpClient can be extended:

import { HttpBackend, HttpClient, HttpInterceptor } from '@angular/common/http';
import { Injectable, InjectionToken, Injector } from '@angular/core';
import { CoreModule } from '../core.module';
import { CustomInterceptorHandler } from './custom-http.handler';

export const API_HTTP_INTERCEPTORS = new InjectionToken<HttpInterceptor[]>('API_HTTP_INTERCEPTORS');

@Injectable({ providedIn: CoreModule })
export class ApiHttpService extends HttpClient {
  constructor(backend: HttpBackend, injector: Injector) {
    super(new CustomInterceptorHandler(backend, injector, API_HTTP_INTERCEPTORS));
  }
}

Finally the new HttpClient along with the interceptors can be injected into the dependency tree:

@NgModule({
  imports: [BrowserModule, BrowserAnimationsModule, RouterModule, HttpClientModule],
  exports: [DefaultLayoutComponent],
  declarations: [DefaultLayoutComponent],
  providers: [
    ApiHttpService,
    { provide: BASE_API_URL, useValue: environment.api },
    { provide: API_HTTP_INTERCEPTORS, useClass: BaseUrlInterceptor, multi: true },
    { provide: API_HTTP_INTERCEPTORS, useClass: ResponseTransformerInterceptor, multi: true }
  ]
})
export class CoreModule {}

Upvotes: 3

Related Questions