In Angulars HttpInterceptor reference, I've found the following section:
To use the same instance of
HttpInterceptors
for the entire app, import theHttpClientModule
only in yourAppModule
, and add the interceptors to the root application injector . If you importHttpClientModule
multiple times across different modules (for example, in lazy loading modules), each import creates a new copy of theHttpClientModule
, 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.
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 {}