Angular 6+是完整的依赖项注入指南。 ProvideIn与提供者:[]

图片

Angular 6引入了一种新的改进语法,用于将服务依赖项嵌入到应用程序中( ProvideIn )。 尽管已经发布了Angular 7,但该主题仍然有意义。 GitHub,Slack和Stack Overflow的评论中有很多困惑,所以让我们仔细看一下这个主题。

在本文中,我们将考虑:


  1. 依赖注入
  2. 将依赖项注入Angular的旧方法( provider:[] );
  3. 一种在Angular中注入依赖项的新方法( providerIn:'root'| SomeModule );
  4. UseIn场景ProvideIn ;
  5. 在应用程序中使用新语法的建议;
  6. 总结一下。

依赖注入


如果您已有关于DI的想法,则可以跳过本节。
依赖注入DI )是一种创建依赖于其他对象的对象的方法。 依赖项注入系统在实例化类时提供依赖对象。

-Angular文档

形式上的解释是好的,但是让我们仔细看看什么是依赖注入。

所有组件和服务都是类。 每个类都有一个特殊的构造方法,该方法在被调用时会创建该类的实例对象,该实例对象将在应用程序中使用。

假设我们的一项服务中包含以下代码:

constructor(private http: HttpClient) 

如果在不使用依赖项注入机制的情况下创建它,则必须手动添加HttpClient 。 然后,代码将如下所示:

 const myService = new MyService(httpClient) 

但是在这种情况下从哪里获取httpClient ? 还需要创建:

 const httpClient = new HttpClient(httpHandler) 

但是现在从哪里获取httpHandler ? 依此类推,直到实例化所有必需的类。 如我们所见,手动创建可能很复杂,并且在此过程中可能会发生错误。

Angular依赖注入机制自动完成了所有这一切。 我们需要做的就是在组件构造函数中指定依赖项,而无需我们费力地添加它们。

在Angular中注入依赖项的旧方法(提供者:[])


为了运行该应用程序,Angular需要了解我们要在组件和服务中实现的每个对象。 在Angular 6发行之前,唯一的方法是在provider属性中指定服务:[]装饰器@NgModule@Component@Directive

图片

提供者的三种主要用途:[]

  1. 在立即加载的模块的@NgModule装饰器中( eager );
  2. 在延迟加载模块的@NgModule装饰器中( lazy );
  3. 在装饰器中@Component@Directive

与应用程序一起下载的模块(急切)


在这种情况下,服务在全局范围内注册为单例。 即使包含在多个模块的提供者[]中,它也将是一个单例。 创建服务类的单个实例,该实例将在应用程序的根级别注册。

延迟加载模块(延迟)


连接到惰性模块的服务的实例将在其初始化期间创建。 将这样的服务添加到模块的急切组件中将导致错误: MyService没有提供者! 错误

在@ Component和@ Directive中的实现


当在组件或指令中实现时,将创建服务的单独实例,该实例将在此组件和所有子代中可用。 在这种情况下,该服务将不是单例的,每次使用该组件时都会创建其实例,并在从DOM中删除该组件时将其删除。

图片

在这种情况下, RandomService不会在模块级别实现,并且不是单例,
但已向RandomComponent组件的提供者注册[] 。 结果,每次使用<randm> </randm>时,我们都会得到一个新的随机数。

在Angular中注入依赖关系的新方法(provedIn:'root'| SomeModule)


在Angular 6中,我们获得了一个新的“可树晃动的提供程序”工具,用于依赖项注入到应用程序中,可以使用@Injectable装饰器的ProvidedIn属性来使用

您可以想象到ProvideIn是相反方向上的依赖关系的实现:在模块描述将与其连接的服务之前,现在该服务定义了与其连接的模块。

该服务可以嵌入在应用程序的根目录中( provideIn :'root' )或任何模块中( providerIn:SomeModule )。 includedIn :'root'AppModule中实现的缩写。

图片

让我们分析使用新语法的主要方案:

  1. 在应用程序的根模块中实现( providerIn:'root' );
  2. 在立即加载的模块中执行( eager );
  3. 在延迟加载的模块中实现( 惰性 )。

在应用程序的根模块中实现(providerIn:“ root”)


这是最常见的依赖项注入选项。 在这种情况下,仅当服务实际使用时,即该服务才会添加到捆绑应用中。 嵌入组件或其他服务中。

使用新方法时,在使用所有书面服务的整体式SPA应用程序中不会有太大区别,但是providerIn:在编写库时, “ root”将很有用。

以前,所有库服务都需要添加到提供者:其模块的[] 。 将库导入应用程序后,即使仅使用了一项服务,所有服务也都添加到了捆绑软件中。 在provideIn:'root'的情况下,无需连接库模块。 只需将服务嵌入所需的组件中即可。

延迟加载模块(延迟)和providerIn:“ root”


如果在惰性模块中使用providerIn :'root'实现服务,会发生什么?

从技术上讲, “ root”代表AppModule ,但是Angular足够聪明,可以将服务添加到模块的惰性捆绑中(如果仅在其组件和服务中实现)。 但这是一个问题(尽管有人声称这是一个功能)。 如果以后将仅在惰性模块中使用的服务引入主模块,则该服务将被转移到主捆绑包中。 在具有许多模块和服务的大型应用程序中,这可能导致依赖项跟踪问题和不可预测的行为。

小心点! 在多个模块中实现一项服务会导致隐藏的依赖关系,这些依赖关系很难理解,无法解开。

幸运的是,有一些方法可以防止这种情况的发生,我们将在下面进行介绍。

立即加载模块中的依赖注入(急切)


通常,这种情况没有意义,我们可以使用providerIn:'root'代替它。 在EagerModule中连接服务可用于封装,并且会在不连接模块的情况下阻止实现,但是在大多数情况下,这不是必需的。

如果您确实需要限制服务范围,那么使用旧的provider:[]方法会更容易,因为它肯定不会导致循环依赖。

如果可能,请尝试在所有渴望的模块中使用providerIn:'root'。

注意事项 延迟加载模块的优势(懒惰)


Angular的主要功能之一是能够轻松地将应用程序拆分成多个片段,这具有以下优点:

  1. 应用程序主捆绑包的尺寸较小,因此应用程序加载和启动速度更快;
  2. 延迟加载模块是很好隔离的,并且在相应路由的loadChildren属性中一次连接到了应用程序。

由于延迟加载,可以轻松地删除包含数百个服务和组件的整个模块,或者将其移动到单独的应用程序或库中。

隔离惰性模块的另一个优点是,在其中犯了一个错误不会影响应用程序的其余部分。 现在,即使在发布当天,您也可以安然入睡。

在延迟加载的模块中实现(providerIn:LazyModule)


图片

依赖注入到特定模块中会阻止在应用程序的其他部分中使用该服务。 这保留了依赖性结构,这对于杂乱的依赖性注入可能导致混乱的大型应用程序尤其有用。

有趣的事实:如果在应用程序的主要部分中实现了懒惰服务,则程序集(甚至AOT)将失败而不会出现错误,但是应用程序将崩溃并显示“没有提供LazyService的提供程序”错误。

循环依赖问题


图片

您可以按照以下方式重现该错误:

  1. 创建LazyModule模块;
  2. 我们创建LazyService服务,并使用provideIn:LazyModule进行连接;
  3. 我们创建LazyComponent组件并将其连接到LazyModule
  4. LazyService添加到LazyComponent组件的构造函数中;
  5. 我们得到一个与周期相关的错误。

示意图如下: service-> module-> component-> service

您可以通过创建子模块LazyServiceModule来解决此问题,该子模块将连接到LazyModule 。 将服务连接到子模块。
图片

在这种情况下,您将不得不创建一个附加模块,但是不需要太多的工作,并且具有以下优点:

  1. 它将阻止在其他应用程序模块中引入服务;
  2. 仅当服务嵌入在模块中使用的组件或其他服务中时,该服务才会添加到捆绑软件中。

将服务嵌入组件(providerIn:SomeComponent)


是否可以使用新语法在@Component@Directive中嵌入服务?

暂时没有!

要为每个组件创建服务的实例,您仍然需要使用@ omponent@Directive装饰器中的provider:[]

图片

在应用程序中使用新语法的最佳实践


图书馆


ProvidedIn:'root'是创建库的好方法。 这是将功能的直接使用的部分仅连接到主应用程序并减小最终组件尺寸的真正方便的方法。

一个实际的例子是ngx-model库,该库已使用新语法重写,现在称为@ angular-extensions / model 在新的实现中,无需将NgxModelModule连接到应用程序,仅将ModelFactory嵌入到必要的组件中就足够了。 可以在此处找到实现的详细信息。

延迟下载模块(延迟)


使用单独的provideIn:LazyServicesModule模块提供服务,并将其插入LazyModule 。 这种方法封装了服务,并防止将它们插入其他模块。 这将设置边界并帮助创建可伸缩的体系结构。

以我的经验,偶然将其引入主模块或附加模块(使用providerIn:'root')会导致混乱,并且不是最佳解决方案!

ProvidedIn :'root'也可以正常工作,但是在其他类中使用provideIn:LazyServideModule时 ,会出现“缺少提供程序”错误,并且可以修复该体系结构。 将服务移至应用程序主体中更合适的位置。

什么时候应该使用提供者:[]?


如果有必要配置模块。 例如,仅将服务连接到SomeModule.forRoot(someConfig)

图片

另一方面,在这种情况下,可以使用providerIn:'root'。 这将确保仅将服务添加到应用程序一次。

结论


  1. 使用providerIn:'root'将服务注册为单例,在整个应用程序中都可用。
  2. 对于主捆绑包中包含的模块,请使用provideIn :'root' ,而不是providerIn:EagerlyImportedModule 。 在特殊情况下,请使用提供程序:[]进行封装。
  3. 使用服务来创建子模块,以限制其提供的范围使用延迟加载时提供的LazyServiceModule
  4. LazyServiceModule模块插入LazyModule中,以防止循环依赖。
  5. @ omponent@Directive装饰器中使用提供程序:[]为每个新组件实例创建一个新服务实例。 服务实例也将在所有子组件中可用。
  6. 始终限制依赖项的范围,以改进体系结构并避免混淆依赖项。

参考文献


原始文章。
Angular是俄语社区。
俄罗斯的角聚会

Source: https://habr.com/ru/post/zh-CN429342/


All Articles