Angular 6+:在非根模块中使用ProvidedIn引起了循环依赖问题

51
我正在尝试通过新的providedIn属性来提供解析服务。
这是我在受保护的模块中使用的翻译解析器:

import { Injectable } from '@angular/core';

import { Observable , pipe } from 'rxjs';
import {map} from "rxjs/operators";

//This is causing: "WARNING in Circular dependency detected:"
import {ProtectedModule} from "../../../protected/protected.module";

import { HttpHandlerService } from '../../http/http-handler.service';

@Injectable({
  providedIn: ProtectedModule //Over here (I need the import for this line)
})
export class TranslationsResolverService {
  constructor(private _httpHandlerService : HttpHandlerService) { }
    resolve(): any {
      //Do Something...
    }
}

我在受保护的路由模块中声明了翻译解析器服务:

import { NgModule }           from '@angular/core';
import {RouterModule, Routes} from '@angular/router';

import {AuthGuard} from "../core/resolvers/auth/auth.guard";
import {TranslationsResolverService} from "./../core/resolvers/translations/translations-resolver.service";

const routes: Routes = [
  {
    path : 'app' ,
    component: ProtectedComponent,
    resolve : {
      translations : TranslationsResolverService // <---- Over here - i can't remove that of course
    },
    canActivate: [AuthGuard],
    ]
  }
];


@NgModule({
  imports : [RouterModule.forChild(routes)],
  exports : [RouterModule]
})
export class ProtectedRoutingModule { }

由于我在 translations-resolver.service.ts 中导入(typescript import)protected.module以便在 providedIn 属性中使用它,所以会出现循环依赖警告:

path/to/translations-resolver.service.ts -> 

protected/protected.module.ts ->

protected/protected-routing.module.ts -> 

path to translations-resolver.service.ts

第二个路径(protected/protected.module.ts)是由于providedIn属性而添加的。
我可以通过将translationsResolver提供为NgModule provider(在providers数组中)来解决此问题,但我更喜欢它作为一个injectable provider。
有什么解决方法的建议吗?

除非有一种情况,你希望服务仅在使用者导入特定的 @NgModule 时才可用,否则应始终在根注入器中提供你的服务。参考:https://angular.io/guide/providers#provider-scope - Krishna Mohan
从 Angular 10 开始,您还可以在组件中声明提供程序,这些提供程序仅限于该组件层次结构。 - S.D.
在 Angular 9+ 中,您可以使用 providerIn: any,请查看我的答案。 - maxisam
5个回答

28

我想标记这个答案,但我希望有人能为这个问题提供解决方案。每次我尝试在模块范围内(而不是根目录)使用特定服务时,都会抛出一些循环依赖警告。 - RtmY
2
无论如何,这都是一个糟糕的答案 :-) - DarkNeuron
1
那个被引用的评论得到了非常糟糕的投票结果 - 而且评论中说:“使用可树摇动的提供程序,您现在没有理由从模块提供服务(某些懒加载服务与使用来自单独懒加载模块的服务的组件存在一种罕见情况)。” 所以这并不是零理由 - 实际上正是我的确切情况。 - Simon_Weaver
确实令人失望,尽管我必须说到目前为止这并没有给我带来任何问题。 - RJM
使用"providedIn: any",在Angular 9之后,查看我的答案。 - maxisam
我很高兴知道我一直在做正确的事情,哈哈,但我感觉不太好,哈哈。 - minigeek

22

更新 - 2019年10月

我的回答现已获得5个赞,所以我想坦白说一下,我不再遵循以下内容的建议了!

由于官方(并且被广泛遵循)的Angular政策是使用providedIn:'root',我决定总的来说如果我坚持使用这个方法可能会让其他开发人员感到更加困惑。到目前为止,这没有给我带来任何问题,但下面的注意点仍然存在,我认为保持警惕非常重要。

原始帖子

我认为Angular在providedIn语法方面有些混乱,似乎已经困扰了许多人。例如,请参考以下两个github线程:

providedIn语法似乎具有两个主要的优点

  1. 它支持未使用服务的摇树功能
  2. providedIn: 'root'确保您只获得服务的一个实例

如果你在写一个库而不是应用程序,那么你只需要(1)(因为你的应用程序为什么要包含你不需要的服务),你可以通过确保不导入服务模块超过一次来避免多个服务实例 (2)。

providedIn 语法的问题如下:

  1. providedIn: 'root' 打破了服务与其所在模块之间的关联 - 因为服务不知道模块,模块也不知道服务。这意味着服务不再真正“属于”该模块,并且将与引用它的任何内容捆绑在一起。这反过来又意味着现在由服务的消费者来确保服务可注入的依赖项(如果有的话)在使用服务之前可用,这很令人困惑并且相当反直觉(除非当然所有依赖项及其依赖关系等都是 providedIn: 'root',在这种情况下,它们将自行解决)。
  2. 上述的循环引用问题。实际上,在此语法中,如果服务实际上被同一模块内的任何组件使用,则不可能保留服务与其模块之间的链接。

这与官方Angular指导相反,但我的建议是:除非你正在编写需要Tree-shaking的第三方库,否则不要使用providedIn - 而是在模块上使用旧的(未弃用)providers语法,例如:

@NgModule({ providers: [MyService], })


4
你应该将补充说明放在你的回答顶部,类似于“更新于2019年10月”,这样人们在阅读旧回答之前会先看到它。(节省不必要的阅读时间和避免混淆) - takanuva15
在Angular 9之后,请使用providedIn: any。 - maxisam
1
@maxisam,any 不是用来做那个的。 - minigeek
1
@maxisam,你的回答帮助我更好地理解了。谢谢...请查看我的新评论。 - minigeek
1
@maxisam 是的,你说得对 :). 我在解析器中遇到过很多次这种情况。使用任何一个都有助于 OP 实现他想要的目标。但大多数答案都在谈论不是 OP 的要求,而是一种替代方案。 - minigeek
显示剩余5条评论

17

这不是一个Angular依赖问题。

TypeScript编译器在尝试解决循环导入时会生成循环引用。

第一种解决方案

创建一个名为ProtectedResolversModule的新模块,并使用providedIn:ProtectedResolversModule将解析器移动到该模块中。

现在,您可以将该模块导入到ProtectedModule中,在加载ProtectedRoutingModule时不会出现循环依赖错误。

第二种解决方案

使用ProtectedModuleproviders数组。


5
我没有理解第一个解决方案,请您能否提供一些代码。 - bharath muppa
9
第二个解决方案使其无法被摇树优化。 - Murhaf Sousli
Angular 9有另一种解决方案,即any。 - maxisam

2
在 Angular9+ 中,您可以使用 providerIn: Any。基本上它的工作方式和模块类似,但是您不直接使用模块,因此不再存在循环依赖问题。文档链接:https://angular.io/api/core/Injectable#options。其中 'any' 表示在每个惰性加载的模块中提供一个唯一的实例,而所有急切加载的模块共享一个实例。换句话说,它位于不同的注入树中。它不是您在其他模块中使用的相同实例。更多参考资料。

https://dev.to/christiankohler/improved-dependeny-injection-with-the-new-providedin-scopes-any-and-platform-30bb

“任意”在模块边界内确保服务是单例非常有帮助。它是一个强大的替代方案,可以确保各个模块之间没有副作用,比如“根”。

https://indepth.dev/posts/1151/a-deep-dive-into-injectable-and-providedin-in-ivy

提供者代码
  private injectableDefInScope(def: ɵɵInjectableDef<any>): boolean {
    if (!def.providedIn) {
      return false;
    } else if (typeof def.providedIn === 'string') {
      return def.providedIn === 'any' || (def.providedIn === this.scope);
    } else {
      return this.injectorDefTypes.has(def.providedIn);
    }
  }

0

请查看来自angular/core的forwardRef()函数。它允许引用尚未定义的引用。

import {MyService} from './service';

constructor(@Inject(forwardRef(() => MyService)) public myService: MyService) {
}

Ozgur,我不确定这怎么能帮助我解决循环依赖问题。 - RtmY
不要在路由中注入您的服务,而是使用构造函数。 - Ozgur
我必须在路由中“注入”(更正确的说法是注册)解析器以进行解析阶段,并且我想使用providedIn语法来标记解析器服务的范围。根据您的解决方案,我应该删除/更改哪一个? - RtmY
这不太好。只要有import语句,问题就无法解决。 - Avius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接