官方文档

Angular 中有两个注入器层次结构:

(1) ModuleInjector 层次结构 —— 使用 @NgModule() 或 @Injectable() 注解在此层次结构中配置 ModuleInjector。

(2) ElementInjector 层次结构 —— 在每个 DOM 元素上隐式创建。除非你在 @Directive() 或 @Component() 的 providers 属性中进行配置,否则默认情况下,ElementInjector 为空。

意思是,只要我们在 @NgModule 里通过 providers 数组定义服务提供者,以及在服务实现类里使用 @Injectable 注解,我们实际上就在定义 ModuleInjector.

ModuleInjector

可以通过以下两种方式之一配置 ModuleInjector :

  • 使用 @Injectable() 的 providedIn 属性引用 @NgModule() 或 root。
  • 使用 @NgModule() 的 providers 数组。

使用 @Injectable() 的 providedIn 属性优于 @NgModule() 的 providers 数组,因为使用 @Injectable() 的 providedIn 时,优化工具可以进行 tree shaking,从而删除你的应用程序中未使用的服务,以减小捆绑包尺寸。

下面是通过 NgModule.providers 定义 ModuleInjector 注释:

@usageNotes — Dependencies whose providers are listed here become available for injection into any component, directive, pipe or service that is a child of this injector. The NgModule used for bootstrapping uses the root injector, and can provide dependencies to any part of the app.
A lazy-loaded module has its own injector, typically a child of the app root injector. Lazy-loaded services are scoped to the lazy-loaded module’s injector. If a lazy-loaded module also provides the UserService, any component created within that module’s context (such as by router navigation) gets the local instance of the service, not the instance in the root injector. Components in external modules continue to receive the instance provided by their injectors.

子 ModuleInjector 是在惰性加载其它 @NgModules 时创建的。
使用 @Injectable() 的 providedIn 属性提供服务的方式如下:

import { Injectable } from '@angular/core';@Injectable({providedIn: 'root'  // <--provides this service in the root ModuleInjector
})
export class ItemService {name = 'telephone';
}

@Injectable() 装饰器标识服务类。该 providedIn 属性配置指定的 ModuleInjector,这里的 root 会把让该服务在 root ModuleInjector 上可用。

平台注入器

在 root 之上还有两个注入器,一个是额外的 ModuleInjector,一个是 NullInjector()。

思考下 Angular 要如何通过 main.ts 中的如下代码引导应用程序:

platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {...})

bootstrapModule() 方法会创建一个由 AppModule 配置的注入器作为平台注入器的子注入器。也就是 root ModuleInjector。
platformBrowserDynamic() 方法创建一个由 PlatformModule 配置的注入器,该注入器包含特定平台的依赖项。这允许多个应用共享同一套平台配置。例如,无论你运行多少个应用程序,浏览器都只有一个 URL 栏。你可以使用 platformBrowser() 函数提供 extraProviders,从而在平台级别配置特定平台的额外提供者。

层次结构中的下一个父注入器是 NullInjector(),它是树的顶部。如果你在树中向上走了很远,以至于要在 NullInjector() 中寻找服务,那么除非使用 @Optional(),否则将收到错误消息,因为最终所有东西都将以 NullInjector() 结束并返回错误,或者对于 @Optional(),返回 null。

NullInjector 相当于注入器机制的错误处理,default 机制。

下图展示了前面各段落描述的 root ModuleInjector 及其父注入器之间的关系。

ElementInjector

Angular 会为每个 DOM 元素隐式创建 ElementInjector。
可以用 @Component() 装饰器中的 providers 或 viewProviders 属性来配置 ElementInjector 以提供服务。例如,下面的 TestComponent 通过提供此服务来配置 ElementInjector:

@Component({...providers: [{ provide: ItemService, useValue: { name: 'lamp' } }]
})
export class TestComponent

这地方有点费解,关 DOM 什么事?

@Directive() 和 @Component()

组件是一种特殊类型的指令,这意味着 @Directive() 具有 providers 属性,@Component() 也同样如此。 这意味着指令和组件都可以使用 providers 属性来配置提供者。当使用 providers 属性为组件或指令配置提供者时,该提供程商就属于该组件或指令的 ElementInjector。同一元素上的组件和指令共享同一个注入器。

解析规则

当为组件/指令解析令牌时,Angular 分为两个阶段来解析它:

  • 针对 ElementInjector 层次结构(其父级)
  • 针对 ModuleInjector 层次结构(其父级)

当组件声明依赖项时,Angular 会尝试使用它自己的 ElementInjector 来满足该依赖。 如果组件的注入器缺少提供者,它将把请求传给其父组件的 ElementInjector。

这些请求将继续转发,直到 Angular 找到可以处理该请求的注入器或用完祖先 ElementInjector。

如果 Angular 在任何 ElementInjector 中都找不到提供者,它将返回到发起请求的元素,并在 ModuleInjector 层次结构中进行查找。如果 Angular 仍然找不到提供者,它将引发错误。

如果你已在不同级别注册了相同 DI 令牌的提供者,则 Angular 会用遇到的第一个来解析该依赖。例如,如果提供者已经在需要此服务的组件中本地注册了,则 Angular 不会再寻找同一服务的其它提供者。

解析修饰符

解析修饰符分为三类:

  • 如果 Angular 找不到你要的东西该怎么办,用 @Optional()
  • 从哪里开始寻找,用 @SkipSelf()
  • 到哪里停止寻找,用 @Host() 和 @Self()

默认情况下,Angular 始终从当前的 Injector 开始,并一直向上搜索。修饰符使你可以更改开始(默认是自己)或结束位置。

@Optional()

@Optional() 允许 Angular 将你注入的服务视为可选服务。这样,如果无法在运行时解析它,Angular 只会将服务解析为 null,而不会抛出错误。在下面的范例中,服务 OptionalService 没有在 @NgModule() 或组件类中提供,所以它没有在应用中的任何地方。

export class OptionalComponent {constructor(@Optional() public optional?: OptionalService) {}
}

@Self()

使用 @Self() 让 Angular 仅查看当前组件或指令的 ElementInjector。
@Self() 的一个好例子是要注入某个服务,但只有当该服务在当前宿主元素上可用时才行。为了避免这种情况下出错,请将 @Self() 与 @Optional() 结合使用。

ElementInjector 用例范例

场景:服务隔离

出于架构方面的考虑,可能会让你决定把一个服务限制到只能在它所属的那个应用域中访问。 比如,这个例子中包括一个用于显示反派列表的 VillainsListComponent,它会从 VillainsService 中获得反派列表数据。
如果你在根模块 AppModule 中(也就是你注册 HeroesService 的地方)提供 VillainsService,就会让应用中的任何地方都能访问到 VillainsService,包括针对英雄的工作流。如果你稍后修改了 VillainsService,就可能破坏了英雄组件中的某些地方。在根模块 AppModule 中提供该服务将会引入此风险。
该怎么做呢?你可以在 VillainsListComponent 的 providers 元数据中提供 VillainsService,就像这样:

@Component({selector: 'app-villains-list',templateUrl: './villains-list.component.html',providers: [ VillainsService ]
})

在 VillainsListComponent 的元数据中而不是其它地方提供 VillainsService 服务,该服务就会只在 VillainsListComponent 及其子组件树中可用。
VillainService 对于 VillainsListComponent 来说是单例的,因为它就是在这里声明的。只要 VillainsListComponent 没有销毁,它就始终是 VillainService 的同一个实例。但是对于 VillainsListComponent 的多个实例,每个 VillainsListComponent 的实例都会有自己的 VillainService 实例。

每个组件的实例都有它自己的注入器。 在组件级提供服务可以确保组件的每个实例都得到一个自己的、私有的服务实例。

场景:专门的提供者

这就是 SAP Spartacus 典型的应用场景。

在其它层级重新提供服务的另一个理由,是在组件树的深层中把该服务替换为一个更专门化的实现。

代码第13行的 BudgetCostCenterListService 是 ListService 的一个具体实现。

更多Jerry的原创文章,尽在:“汪子熙”:

什么是 Angular 的多级注入器相关推荐

  1. angular 注入器配置_Angular2 多级注入器详解及实例

    angular2 的依赖注入包含了太多的内容,其中的一个重点就是注入器,而注入器又非常难理解,今天我们不深入介绍注入器的内容,可以参考官方文档,我们今天来说注入器的层级. 也就是组件获取服务的容器会选 ...

  2. angular 注入器配置_angular依赖注入

    一.imports,declarations,providers介绍 imports中写入的是当前模块导入的其他模块,故imports应该导入的是module:declarations中写入的是当前模 ...

  3. Angular 实现树形菜单(多级菜单)功能模块

    前言 本文要分享的是一个多级菜单效果,也就是传说中的树形结构菜单,理论上支持无限级菜单,当然数据结构要一定的要求,但这都不是什么难事,因为我们可以把数据组装成所需要的结构.下面这个例子虽然不是很完美好 ...

  4. Angular 中的依赖注入link

    Angular 中的依赖注入link 依赖注入(DI)是一种重要的应用设计模式. Angular 有自己的 DI 框架,在设计应用时常会用到它,以提升它们的开发效率和模块化程度. 依赖,是当类需要执行 ...

  5. Angular 通过依赖注入机制注入一个对象的例子,什么是 ElementInjector

    假设我在app.config.ts里定义了一个interface AppConfig和一个对象HERO_DI_CONFIG, 我想将后者注入到一个类的构造函数里去: export interface ...

  6. 转发: Angular装饰器

    Angular中的装饰器是一个函数,它将元数据添加到类.类成员(属性.方法)和函数参数. 用法:要想应用装饰器,把它放在被装饰对象的上面或左边. Angular使用自己的一套装饰器来实现应用程序各部件 ...

  7. angular的injector

    angular.injector( [' '] ) 创建注入器的时候(注册服务),指定需要加载的模块; angular.injector()可以调用多次,每次都返回新建的injector对象; inj ...

  8. Angular2 依赖注入

    1. 使用DI 依赖注入是一个很重要的程序设计模式. Angular 有自己的依赖注入框架,离开了它,我们几乎没法构建 Angular 应用.它使用得非常广泛,以至于几乎每个人都会把它简称为 DI. ...

  9. AngularJS进阶学习

    参考:http://***/class/54f3ba65e564e50cfccbad4b 1. AJAX:Asynchronous JavaScript and XML(异步的 JavaScript ...

最新文章

  1. 说说后台开发的一些事
  2. 2.3.1 进程的同步与互斥
  3. [译]ASP.NET Core 2.0 带初始参数的中间件
  4. NodeJS 学习记录
  5. python创建nc文件_如何python写nc文件
  6. 背包问题(动态规划)
  7. 论文浅尝 - TACL2020 | TYDI QA:Google 发表一个多语言的问答语料库
  8. org.attoparser.ParseException: Could not parse as expression: “
  9. 修改goods对ECshop的url路径进行优化
  10. mysql支持数据安全的引擎_2、MySQL常见数据库引擎及比较?
  11. Redis实战(11)高级特性(3)持久化
  12. hdu 1213 “How Many Tables”(并查集基本到优化)
  13. sort目标跟踪算法
  14. 美团2021届秋季校园招聘笔试真题解析:小美的仓库整理
  15. 英伟达 jetson xavier agx 开发(1)开发环境搭建
  16. 感冒发烧没想象中可怕:人体自我保护清除体内感染
  17. roboone机器人_ROBOONE机器人这个品牌怎么样?是否可以加盟投资?
  18. python使用selenium + PhantomJs搭建的简单漫画爬虫工具
  19. Centos 安装OpenStack
  20. 【线代】 线性方程组的解

热门文章

  1. 浅谈MySQL的七种锁
  2. linux配置文件、日志文件全备份
  3. XTU 1250 Super Fast Fourier Transform
  4. elixir 高可用系列(五) Supervisor
  5. linux cron计划任务
  6. 开源项目使用经验原则
  7. 在oracle下我们如何正确的执行数据库恢复
  8. Javascript Throttle Debounce
  9. 每个[NET]开发人员现在应该下载的十种必备工具
  10. 7_23 day26 14min面向对象总结