将 TranslateService 注入拦截器时的 Angular 循环依赖

Posted

技术标签:

【中文标题】将 TranslateService 注入拦截器时的 Angular 循环依赖【英文标题】:Angular Circular dependency when inject TranslateService to interceptor 【发布时间】:2021-07-13 01:50:00 【问题描述】:

我在将依赖项注入拦截器时遇到问题。我想将 TranslateService 注入 HttpErrorInterceptor,但出现循环依赖错误。当我删除 TranslateService 注入时,一切正常。

我已经在我的 app.module.ts 中声明了拦截器。 我的应用模块如下所示:

@NgModule(
 declarations: [
   AppComponent
 ],
 imports: [
   BrowserModule,
   BrowserAnimationsModule,
   CoreModule,
   HttpClientModule,
   TranslateModule.forRoot(
   loader: 
      provide: TranslateLoader,
      useFactory: HttpLoaderFactory,
      deps: [HttpClient],
   ,
   defaultLanguage: 'pl-pl'
 ),
   AppRoutingModule,
   RouterModule,
   FormsModule,
   ReactiveFormsModule,
   ToastrModule.forRoot()
 ],
 providers: [
   
     provide: HTTP_INTERCEPTORS,
     useClass: JwtInterceptor,
     multi: true
   ,
   
     provide: HTTP_INTERCEPTORS,
     useClass: HttpErrorInterceptor,
     multi: true,
     deps: [TranslateService, ToastrService]
   
 ],
 bootstrap: [AppComponent]
)
export class AppModule  

在 AppModule 中,我导入了 CoreModule,其中有一个包含拦截器的文件夹,我的 CoreModule 如下所示:

@NgModule(
  declarations: [],
  imports: [
    CommonModule
  ],
  providers: [
    CookieService,
    NoAuthGuard,
    AuthGuard
  ]
)
export class CoreModule  

我把登录页面放在AuthModule中,长这样:

@NgModule(
  declarations: [LoginComponent, AuthComponent, ForgotPasswordComponent],
  imports: [
    CommonModule,
    AuthRoutingModule,
    RouterModule,
    SharedModule
  ],
  providers: [
    AuthService
  ]
)
export class AuthModule  

在 Authmodule 中,我导入了 SharedModule,并在其中导出了 TranslateModule。 SharedModule 看起来像这样:

@NgModule(
  declarations: [],
  imports: [
    CommonModule,
    HttpClientModule,
    ReactiveFormsModule
  ],
  exports: [
    TranslateModule,
    ReactiveFormsModule
  ]
)
export class SharedModule  

我找不到登录页面出现循环依赖错误的原因。

我的假设是我已经将 CoreModule 导入到 AppModule 中,在那里我保留了拦截器、守卫和 SharedModule,它对所有功能模块进行了即兴创作,我想保留例如那里的通用组件。

Błąd, jaki dostaję to:

core.js:6162 ERROR Error: NG0200: Circular dependency in DI detected for InjectionToken HTTP_INTERCEPTORS. Find more at https://angular.io/errors/NG0200
    at throwCyclicDependencyError (core.js:216)
    at R3Injector.hydrate (core.js:11381)
    at R3Injector.get (core.js:11205)
    at HttpInterceptingHandler.handle (http.js:1978)
    at MergeMapSubscriber.project (http.js:1114)
    at MergeMapSubscriber._tryNext (mergeMap.js:44)
    at MergeMapSubscriber._next (mergeMap.js:34)
    at MergeMapSubscriber.next (Subscriber.js:49)
    at Observable._subscribe (subscribeToArray.js:3)
    at Observable._trySubscribe (Observable.js:42)

【问题讨论】:

很可能你有间接循环依赖。 A -> B -> C -> A 而不是直接循环依赖 A -> B -> A 如果这有意义... 也许不相关,但你有两个相同的提供是故意的吗?例如provide: HTTP_INTERCEPTORS。也有点好奇为什么你确实需要在你的拦截器中依赖翻译服务,最后可能在这个相关 SO ***.com/questions/48376571/… 的 cmets 中有提示 @GetOffMyLawn 有可能,但我不知道它可能发生在哪里。 @cYrixmorten 至于 HTTP_INTERCEPTORS 的构造,我有两个不同的拦截器,据我所见,这与拦截器的每个定义一起使用。至于拦截器中的翻译,我想使用 TranslateService 来翻译我从后端得到的错误。 只要按照你对每个文件的所有导入,最终会引用你已经在更高层引用的东西。 【参考方案1】:

您遇到的问题是TranslateModule 的初始化取决于HttpClient,这意味着需要首先初始化HttpClientModule。这会导致 HttpErrorInterceptor 的初始化,因为拦截器是使用 HttpClientModule 初始化的。这会导致循环依赖,因为您的拦截器需要TranslateService。您可以通过在您的HttpErrorInterceptor 中注入Injector 来解决此问题,然后在需要时直接从注入器请求TranslateService。这样可以防止对初始初始化的循环依赖。

由于您没有为您的拦截器提供代码,这里是一个使用这种方法的示例拦截器。

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor 
  constructor(private readonly injector: Injector) 

  public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
    try 
      const translateService = this.injector.get(TranslateService)
      // log using translate service
     catch 
      // log without translation translation service is not yet available
    
  

您仍然需要处理获取翻译服务失败的情况,因为您可能会在加载翻译时遇到错误。

【讨论】:

很好的解释! 很好的解决方案!【参考方案2】:

根据this GitHub 问题,一些人(包括我自己)能够通过删除TranslateModule.forRoot() 中的defaultLanguage 来解决该问题

我的 LanguageModule 实现如下:

@NgModule(
  imports: [
    HttpClientModule,
    TranslateModule.forRoot(
      loader: 
        provide: TranslateLoader,
        useFactory: (createTranslateLoader),
        deps: [HttpClient]
      ,
      isolate: true
    )
  ],
  providers: [
    TranslateService
  ]
)
export class LanguageModule 
  public constructor(translateSvc: TranslateService, http: HttpClient) 
    translateSvc.onLangChange
      .pipe(
        switchMap((currentLang: LangChangeEvent) => zip(
          of(currentLang),
          http.get(`path/to/i18n/$currentLang.lang.json`)
        ))
      ).subscribe(([currentLang, localizations]) => 
        translateSvc.setTranslation(translateSvc.currentLang, localizations, true);
      );

    translateSvc.use(translateSvc.getDefaultLang());
  

然后导入到我的CoreModule

@NgModule(
    imports: [
      CommonModule,
      HttpClientModule,
      BrowserAnimationsModule,
      LanguageModule,
      ...
    ],
    exports: [],
    declarations: [],
    providers: [
      ...
      
        provide: HTTP_INTERCEPTORS,
        useClass: AuthInterceptor,
        multi: true
      
    ]
  )
  export class CoreModule 
    public constructor(@Optional() @SkipSelf() parentModule: CoreModule, private translateSvc: TranslateService) 
      this.translateSvc.use(environment.defaultLang)
    

【讨论】:

以上是关于将 TranslateService 注入拦截器时的 Angular 循环依赖的主要内容,如果未能解决你的问题,请参考以下文章

将 $state (ui-router) 注入 $http 拦截器会导致循环依赖

springBoot Interceptor拦截器注入mapper 为null

mybatis运行时拦截ParameterHandler注入参数

Mybatis的sql注入拦截

“没有 TranslateService 的提供者”错误以某种方式连接到 npm install

为什么你写的拦截器注入不了 Java bean?