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

简介: 本文由达观数据研究院根据《Total Guide To Angular 6+ Dependency Injection — providedIn vs providers》编译,如有不当,还请指正。Angular 6为我们提供了更好的语法——provideIn,用于将服务注册到Angular依赖注入机制中。

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

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

然而,新语法带来了非常多使用上的困惑,在GitHub评论,Slack和Stack Overflow上看到一些开发者经常混淆。所以现在,让我们把这一切都说清楚。

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

  1. 依赖注入回顾(可选)
  2. 使用旧语法进行依赖注入—— providers: []
  3. 使用新语法进行依赖注入—— providedIn: 'root' | SomeModule
  4. providedIn 的使用场景
  5. 在项目中如何使用新语法的最佳实践
  6. 总结

依赖注入

让我们快速回顾一下依赖注入是什么,如果感觉简单,你可以跳过这一小节。

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

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

在我们的服务中,我们都看到过类似于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: [] 中指定服务,如下:
2

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

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

在预加载模块中使用providers: []

在这种情况下,服务将是全局单例的。即使它被多个模块的providers: []重复申明,它也不会重新创建实例。注入器只会创建一个实例,这是因为它们最终都会注册到根级注入器。

在懒加载模块中使用providers: []

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

@Component@Directive中使用providers: []

服务是按组件实例化的,并且可以在组件及其子树中的所有子组件中访问。在这种情况下,服务不是单例的,每次我们在另一个组件的模板中使用组件时,我们都会获得所提供服务的新实例。 这也意味着服务实例将与组件一起销毁......
3

上面图中,RandomServiceRandomComponent中被注册,因此,每当我们在模板中使用<random> </ random>组件时,我们将得到不同的随机数。

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

使用新语法进行依赖注入

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

我们可以将provideIn视为以反向方式指定依赖关系。 现在不是模块申明需要哪些服务,而是服务本身宣布它应该提供给哪些模块使用

申明的模块可以是 root 或其他任何可用模块。另外,root 实际上是 AppModule 的别名,这是一个很好的语法糖,我们因此不需要额外导入 AppModule
4

新语法非常简单,现在让我们实践一下,来探索在应用程序开发过程中可能遇到的一些有趣场景......

使用 providedIn: 'root'

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

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

providedIn 出现之前,需要在主模块的 providers: [] 中注入所有公共服务。然后,组件需要导入该模块,这将导致所有(可能的大量)的服务导入进该组件,即使我们只想使用其中一个服务。

现在,providedIn: 'root'解决了这个问题,我们不需要在模块中导入这些服务,我们要做的仅仅是使用它们。

懒加载 providedIn: 'root' 解决方案
如果我们在懒加载中使用 providedIn: 'root' 来实现服务会发生什么?

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

如果我们又额外将服务注入到其他正常加载的模块中,那么该服务会自动绑定到 mian 的bundle中。

简单来讲:
1、如果服务仅被注入到懒加载模块,它将捆绑在懒加载包中
2、如果服务又被注入到正常模块中,它将捆绑在主包中
这种行为的问题在于,在拥有大量模块和数百项服务的大型应用程序中,它可能变得非常不可预测。

幸运的是,有一种方法可以防止这种情况的发生,我们将在下面的章节中探讨如何加强模块的边界。

使用 providedIn: EagerlyImportedModule

这个解决方案通常没有意义,我们应该坚持使用 provideIn:'root'
它可用于防止应用程序的其余部分注入服务而无需导入相应的模块,但这其实并不是必需的。

附注 - 延迟加载模块的多重好处

Angular最大的优点之一是我们可以非常容易的将应用程序分成完全独立的逻辑块,这有以下好处…
1、更小的初始化代码,这意味着更快的加载和启动时间
2、懒惰加载的模块是真正隔离的。主机应用程序应该引用它们的唯一一点是某些路由的 loadChildren 属性。

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

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

使用 providedIn: LazyLoadedModule

5

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

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

6

幸运的是,我们可以通过创建一个 LazyServiceModule 来避免这个问题,它将是 LazyModule 的一个子模块,并将被用作我们想要提供的所有懒加载服务的“锚”。如下图所示:
7

虽然有点不方便,但我们只需增加一个模块,这种方法结合了两者的优点:

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

新语法能在 @Component@Directive中使用吗?

不,它们并不能。
我们仍然需要在 @Component@Directive 中使用 provider:[]来创建多个服务实例(每个组件)。 目前还没有办法解决这个问题......
8

最佳实践

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

当消费者应用程序只需要可用库功能的一个子集时,它也处理的非常好。只有真正使用的东西才会打包进我们的应用程序中,我们都希望打包出来的文件越小越好。

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

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

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

什么时候使用老的 providers:[] 语法?
我们需要将配置传递给我们的服务吗?
或者换句话说,我们是否有一个使用 SomeModule.forRoot(someConfig) 解决的场景?

在这种情况下,我们仍然需要使用 providers: [],因为新的语法无助于我们定制服务。
另一方面,如果我们曾经使用 SomeModule.forRoot() 来阻止延迟加载模块创建服务的其他实例,我们可以简单地使用 providedIn: 'root' 来实现这一点。
9

总结

  1. providedIn: 'root'用于在整个应用程序中作为单例可用的服务;
  2. 永远不要使用 providedIn:EagerLiymportedmodule,您不需要它,如果有一些非常特殊的用例,那么请使用 providers: [] 来代替;
  3. 使用 providedIn: LazyServiceModule来防止我们将懒加载的服务注入应用程序的正常加载模块;
  4. 如果我们想使用 LazyServiceModule,那么我们必须将其导入 LazyModule,以防止循环依赖警告。然后,LazyModule将以标准方式使用 Angular Router 为某些路由进行懒加载。
  5. 使用 @Component@Directive 内部的 providers: [],为特定的组件子树提供服务,这也将导致创建多个服务实例(每个组件使用一个服务实例)
  6. 始终尝试保守地确定您的服务范围,以防止依赖蔓延和由此产生的巨大混乱。

ABOUT | 关于

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

相关文章
|
18天前
|
测试技术 UED 开发者
“Angular高手必看:模块化与依赖注入的绝妙搭配,让你的代码瞬间高大上!”
【10月更文挑战第25天】本文以问答形式详细探讨了Angular框架中的模块化与依赖注入的高级特性。主要内容包括:Angular模块的基本概念和创建方法,依赖注入的实现方式,模块间依赖关系的处理,以及懒加载模块的使用。通过这些特性,可以提高代码的可维护性和复用性,优化大型Web应用的开发和性能。
26 2
|
4月前
|
前端开发 容器
前端框架与库 - Angular模块与依赖注入
【7月更文挑战第17天】探索Angular的模块化和依赖注入:模块用于组织组件、服务等,通过`@NgModule`声明。依赖注入简化类间依赖管理,但面临模块重复导入、服务作用域不当和依赖循环等问题。解决策略包括规划模块结构、正确设置服务作用域和使用工厂函数打破循环依赖。遵循最佳实践,构建高效、可维护的Angular应用。
63 17
|
4月前
|
设计模式 JavaScript 测试技术
Angular服务与依赖注入机制详解
【7月更文挑战第17天】Angular的服务与依赖注入机制为构建模块化、可维护和可扩展的应用程序提供了强大的支持。通过合理定义和使用服务,以及利用依赖注入来管理依赖关系,我们可以编写出更加清晰、可维护和可测试的代码。希望本文能帮助你更好地理解和应用Angular的服务与依赖注入机制。
|
Java Spring 程序员
Angular4总结(三)—— 依赖注入
依赖注入是控制反转的一种实现方式。这种模式就是Java Web开发中最流行框架Spring的核心概念。 为什么需要使用依赖注入 在原来最普通的开发中,如果我们有一个方法createCar(new Wheel(), new Engine(), new CarBody())。
919 0
|
3月前
|
API 开发者 UED
PrimeFaces:JSF的魔法衣橱,解锁UI设计的无限可能!
【8月更文挑战第31天】本文介绍如何结合 JSF(JavaServer Faces)和 PrimeFaces 构建美观且功能强大的现代用户界面。PrimeFaces 提供丰富的 UI 组件库,包括按钮、输入框、数据网格等,支持现代 Web 标准,简化界面开发。文章通过具体示例展示如何使用 `&lt;p:inputText&gt;` 和 `&lt;p:calendar&gt;` 等组件创建用户表单,并用 `&lt;p:dataTable&gt;` 展示数据集合,提升 JSF 应用的易用性和开发效率。
60 0
|
3月前
|
开发者 安全 SQL
JSF安全卫士:打造铜墙铁壁,抵御Web攻击的钢铁防线!
【8月更文挑战第31天】在构建Web应用时,安全性至关重要。JavaServer Faces (JSF)作为流行的Java Web框架,需防范如XSS、CSRF及SQL注入等攻击。本文详细介绍了如何在JSF应用中实施安全措施,包括严格验证用户输入、使用安全编码实践、实施内容安全策略(CSP)及使用CSRF tokens等。通过示例代码和最佳实践,帮助开发者构建更安全的应用,保护用户数据和系统资源。
52 0
|
3月前
|
开发者 C# C++
揭秘:如何轻松驾驭Uno Platform,用C#和XAML打造跨平台神器——一步步打造你的高性能WebAssembly应用!
【8月更文挑战第31天】Uno Platform 是一个跨平台应用程序框架,支持使用 C# 和 XAML 创建多平台应用,包括 Web。通过编译为 WebAssembly,Uno Platform 可实现在 Web 上运行高性能、接近原生体验的应用。本文介绍如何构建高效的 WebAssembly 应用:首先确保安装最新版本的 Visual Studio 或 VS Code 并配置 Uno Platform 开发环境;接着创建新的 Uno Platform 项目;然后通过安装工具链并使用 Uno WebAssembly CLI 编译应用;最后添加示例代码并测试应用。
104 0