Angular 中每个模块的单独 HttpClient 实例

Separate HttpClient instances per module in Angular

在 Angulars HttpInterceptor 参考中,我 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.

太棒了,这正是我需要的。所以我可以在每个模块中导入 HttpClientModule,这样我的 ApiInterceptor 就会注入基本路径和 JWT 令牌,不会干扰我在 AppTranslationModule 中使用的 HttpClient,获取一些翻译文件。

所以我有我的 AppModule:

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

... 导入 AppTranslationModuleCoreModule。他们每个人都导入 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 {}

不幸的是,来自 CoreModule 的拦截器 (BaseUrlInterceptor) 仍然应用于 AppTranslationModule 中的 HttpClient。如果我正确理解上面提到的文档,这不应该发生吗?知道这里发生了什么吗?

我在 Angular 11.

我在 this blog post 找到了解决方案。正如那里提到的 HttpHandler 需要更改,以创建独立于其他拦截器的 HttpClient 的单独实例:

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);
  }
}

有了这个 HttpClient 可以扩展:

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));
  }
}

最后,新的 HttpClient 连同拦截器可以注入到依赖树中:

@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 {}