达观数据:Angular 6+依赖注入使用指南:providedIn与providers对比

图片描述
本文由达观数据研究院根据《Total Guide To Angular 6+ Dependency Injection — providedIn vs providers》编译,若有不当,还请指正。前端

Angular 6为咱们提供了更好的语法——provideIn,用于将服务注册到Angular依赖注入机制中。架构

然而,新语法带来了很是多使用上的困惑,在GitHub评论,Slack和Stack
Overflow上看到一些开发者常常混淆。因此,如今,让咱们把这一切都说清楚。

接下来咱们将会学习什么?

依赖注入回顾(可选)dom

使用旧语法进行依赖注入—— providers: []ide

使用新语法进行依赖注入—— providedIn: 'root' | SomeModule函数

providedIn 的使用场景学习

在项目中如何使用新语法的最佳实践ui

总结spa

依赖注入

让咱们快速回顾一下依赖注入是什么,若是感受简单,你能够跳过这一小节。3d

依赖注入(DI)是一种建立依赖其余对象的方法。在建立一个新的对象实例时,依赖注入系统将会提供依赖对象(称为依赖关系) - Angular Docs

咱们的组件和服务都是类,每一个类都有一个名为constructor的特殊函数,当咱们想要在咱们的应用程序中建立该类的对象(实例)时调用它。code

在咱们的服务中,咱们都看到过相似于 constructor(private http: HttpClient)这样的代码。假如没有Angular DI机制,咱们必须手动提供HttpClient来建立咱们本身的服务。

咱们的代码会像这样:const myService = new MyService(httpClient);可是,咱们还须要得到httpClient对象。

因而,我须要再实例一个HttpClient:const httpClient = new HttpClient(httpHandler);但httpHandler又从哪来?若是这样建立下去,到底何时是个头。并且,这个过程至关繁琐,并且很容易出错。

幸亏,Angular的DI机制自动地帮咱们完成了上述的全部操做,咱们所要作的只是在组件的构造函数中指定依赖项,组件将会很轻松地就能用到这些依赖。可天下没有免费的午饭...

使用旧语法进行依赖注入

为了让工程实践作的更好,Angular必须了解咱们想要注入到组件和服务中的每个实体。

在Angular 6 发布之前, 惟一的方法是在 providers: [] 中指定服务,以下:

图片描述

根据具体使用场景, providers: [] 将有三种不一样的用法:

一、在预加载的模块的@NgModule装饰器中指定 providers: []
二、在懒加载的模块的@NgModule装饰器中指定 providers: []
三、在@Component和@Directive装饰器中指定 providers: []

在预加载模块中使用providers: []
在这种状况下,服务将是全局单例的。即便它被多个模块的providers: []重复申明,它也不会从新建立实例。注入器只会建立一个实例,这是由于它们最终都会注册到根级注入器。

在懒加载模块中使用providers: []
在应用程序运行初始化后一段时间,懒加载模块中提供的服务实例才会在子注入器(懒加载模块)上建立。若是在预加载模块中注入这些服务,将会报 No provider for MyService! 错误。

在@Component和@Directive中使用providers: []
服务是按组件实例化的,而且能够在组件及其子树中的全部子组件中访问。在这种状况下,服务不是单例的,每次咱们在另外一个组件的模板中使用组件时,咱们都会得到所提供服务的新实例。 这也意味着服务实例将与组件一块儿销毁......

图片描述

上面图中,RandomService 在 RandomComponent中被注册,所以,每当咱们在模板中使用<random> </ random>组件时,咱们将获得不一样的随机数。

若是在模块级别提供 RandomService而且将被做为单例提供,则不会出现这种状况。 在这种状况下,<random> </ random> 组件的每次使用都会显示相同的随机数,由于该数字是在服务实例化期间生成的。

使用新语法进行依赖注入

随着Angular 6的出现,咱们可使用全新的语法在咱们的应用程序中创建依赖项, 官方名称是“Tree-shakable providers”,咱们经过使用 @Injectable 装饰器的新增的 provideIn 属性来使用它。

咱们能够将provideIn视为以反向方式指定依赖关系。 如今不是模块申明须要哪些服务,而是服务自己宣布它应该提供给哪些模块使用

申明的模块能够是 root 或其余任何可用模块。另外,root 其实是 AppModule 的别名,这是一个很好的语法糖,咱们所以不须要额外导入 AppModule。

图片描述

新语法很是简单,如今让咱们实践一下,来探索在应用程序开发过程当中可能遇到的一些有趣场景......

使用 providedIn: 'root'

在大多数状况下,这是对咱们有用的最多见的解决方案。此解决方案的主要好处是,只有真正“使用”这些服务时才会打包服务代码。 “使用”表明注入某些组件或其余服务。

另外一方面,providedIn: 'root' 在代码可复用方面为开发人员带来了巨大的积极影响。

providedIn 出现以前,须要在主模块的 providers: [] 中注入全部公共服务。而后,组件须要导入该模块,这将致使全部(可能的大量)的服务导入进该组件,即便咱们只想使用其中一个服务。

如今,providedIn: 'root'解决了这个问题,咱们不须要在模块中导入这些服务,咱们要作的仅仅是使用它们。

懒加载 providedIn: 'root' 解决方案

若是咱们在懒加载中使用 providedIn: 'root' 来实现服务会发生什么?

从技术上讲,'root'表明 AppModule ,但Angular足够聪明,若是该服务只是在惰性组件/服务中注入,那么它只会绑定在延迟加载的bundle中。

若是咱们又额外将服务注入到其余正常加载的模块中,那么该服务会自动绑定到 mian 的bundle中。

简单来说:

一、若是服务仅被注入到懒加载模块,它将捆绑在懒加载包中

二、若是服务又被注入到正常模块中,它将捆绑在主包中

这种行为的问题在于,在拥有大量模块和数百项服务的大型应用程序中,它可能变得很是不可预测。

幸运的是,有一种方法能够防止这种状况的发生,咱们将在下面的章节中探讨如何增强模块的边界。

使用 providedIn: EagerlyImportedModule

这个解决方案一般没有意义,咱们应该坚持使用 provideIn:'root'。

它可用于防止应用程序的其他部分注入服务而无需导入相应的模块,但这其实并非必需的。

附注 - 延迟加载模块的多重好处
Angular最大的优势之一是咱们能够很是容易的将应用程序分红彻底独立的逻辑块,这有如下好处…

一、更小的初始化代码,这意味着更快的加载和启动时间

二、懒惰加载的模块是真正隔离的。主机应用程序应该引用它们的惟一一点是某些路由的 loadChildren 属性。

这意味着,若是使用正确,能够将整个模块删除或外部化为独立的应用程序/库。可能有数百个组件和服务的模块能够在不影响应用程序其他部分的状况下随意移动,这是很是使人惊奇的!

这种隔离的另外一个巨大好处是,对懒惰模块的逻辑进行更改永远不会致使应用程序的其余部分出错。

使用 providedIn: LazyLoadedModule

图片描述

这个解决方案很是棒,由于它能够帮助咱们防止在所需模块以外使用咱们的服务。在开发大型应用程序时,保持依赖关系图是很是有必要的,由于无约束的无处不在的注入可能会致使没法解决的巨大混乱!

不幸的是,有一个小问题……循环依赖
图片描述

幸运的是,咱们能够经过建立一个 LazyServiceModule 来避免这个问题,它将是 LazyModule 的一个子模块,并将被用做咱们想要提供的全部懒加载服务的“锚”。以下图所示:

图片描述

虽然有点不方便,但咱们只需增长一个模块,这种方法结合了二者的优势:

  1. 它防止咱们将懒加载的服务注入应用程序的正常加载模块
  2. 只有当服务被真正注入其余惰性组件时,它才会打包到服务中

新语法能在 @Component和 @Directive中使用吗?
不,它们并不能。

咱们仍然须要在 @Component 或 @Directive 中使用 provider:[]来建立多个服务实例(每一个组件)。 目前尚未办法解决这个问题......

图片描述

最佳实践


当处理开发库、实用程序或任何其余形式的可重用 Angular 逻辑时,providedIn: 'root'是很是好的解决方案。

当消费者应用程序只须要可用库功能的一个子集时,它也处理的很是好。只有真正使用的东西才会打包进咱们的应用程序中,咱们都但愿打包出来的文件越小越好。

懒加载模块
使用 providedIn: LazyServicesModule,而后由 LazyModule 导入,再由 Angular 路由器惰性加载,以实施严格的模块边界和可维护的架构!

这种方法能够防止咱们将懒加载的服务注入应用程序的正常加载模块

使用providedIn: 'root' , 'root'将会正常工做,服务也会被正确捆绑,可是使用 providedIn: LazyServiceModule 为咱们提供了早期的“missing provider”错误,这是一个很好的早期信号,这有助于咱们从新思考咱们的架构。

何时使用老的 providers:[] 语法?
咱们须要将配置传递给咱们的服务吗?

或者换句话说,咱们是否有一个使用 SomeModule.forRoot(someConfig) 解决的场景?

在这种状况下,咱们仍然须要使用 providers: [],由于新的语法无助于咱们定制服务。

另外一方面,若是咱们曾经使用 SomeModule.forRoot() 来阻止延迟加载模块建立服务的其余实例,咱们能够简单地使用 providedIn: 'root' 来实现这一点。

图片描述

总结

将 providedIn: 'root'用于在整个应用程序中做为单例可用的服务;

永远不要使用 providedIn:EagerLiymportedmodule,您不须要它,若是有一些很是特殊的用例,那么请使用 providers: [] 来代替;

使用 providedIn: LazyServiceModule来防止咱们将懒加载的服务注入应用程序的正常加载模块;

若是咱们想使用 LazyServiceModule,那么咱们必须将其导入 LazyModule,以防止循环依赖警告。而后,LazyModule将以标准方式使用 Angular Router 为某些路由进行懒加载。

使用 @Component 或 @Directive 内部的 providers: [],为特定的组件子树提供服务,这也将致使建立多个服务实例(每一个组件使用一个服务实例)

始终尝试保守地肯定您的服务范围,以防止依赖蔓延和由此产生的巨大混乱!

关于译者

王玉略:达观数据前端开发工程师,负责达观数据前端开发,喜欢探索新技术,致力于将代码与平常生活相结合,提升生活效率。

相关文章
相关标签/搜索