如何理解Angular框架依赖注入

依赖注入(DI)是现在软件开发中常用的一个设计模式,它能显著的增强软件系统的灵活性,可靠性和可测试性。非常多的软件框架使用了DI设计模式,包括大家熟知的Spring (Java平台)等。Angular也使用了DI。

基本语法

举一个场景来帮助说明问题,现在有一个TodoList的组件,依赖于一个BackendService的服务类来提供数据。那么,按照Angular中经典的写法就是:

在服务类的定义中,用Injectable来装饰服务类,声明其是可以被用来注入的

1
2
3
4
5
6
7
8
9
import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root',
})
export class BackendService {
constructor() { }
getTodos() { ... }
}

在组件类的定义中,使用构造函数的参数来接收注入的依赖类实例

1
2
3
4
export class TodoList {
constructor(backendService: BackendService) {}
...
}

这两个类的关系如图所示:
依赖注入

在上图中,大家可以看到:生成BackendService的实例并注入到TodoList中这个动作实际上是由注入器(Injector)和构造器(Provider)共同来完成的,Injector主要完成注入,Provider用来产生对象的实例,就好像在别的框架中的工厂一样(Factory)。注入器(Injector)是有Angular框架来维护的,每个注入器中可以包括零个或多个构造器(Provider)

注入器(Injector)的分类

在Angular中,有三类或者叫三个层级的注入器(Injector)可供使用:

  1. 组件级 - Component
  2. 模块级 - NgModule
  3. 全局 - root

这些注入器都可以包含零个或多个构造器,当类需要注入依赖的时候,系统在当前层级进行查找,看看是否有合适的构造器,如果没有,则交给上一级的注入器进行查找。

再次重申,注册器(Injector)是由Angular框架维护的,我们提供的只是构造器

依赖类的查找顺序

在应用运行时,依赖查找的顺序是:首先在组件级注册器中查找是否有对应依赖类的Provider,如果找不到,则到上一级组件的注入器中查找,如果上一级逐渐找不到,就再上一级,组件查找完后,就到组件所属的模块级注册器进行查找,如果仍然找不到,就到全局进行查找。如果最后在全局也没找到,则报错。

以本文开头的代码例子来说,就是Angular框架知道TodoList需要一个BackendService的依赖,它首先查找TodoList自身的注入器(注意: 注入器不是我们维护的,是框架自身维护的,所以每个组件其实都有一个注入器)看看是否有BackendService的构造器,当然在本例中是找不到(因为没声明),然后就在TodoList所属的模块的注入器中查找,看看是否有BackendService的构造器,当然还是找不到, 最后只能到全局注入器中去查找,发现在全局注入器中含有BackendService的构造器。这样就通过全局的注入器完成了依赖注入。

从以上的查找路径可以看到,依赖查找的顺序是从底往上的,也就是说如果有相同的依赖类,底层定义的会覆盖上层定义的。

Provider如何声明

  1. 全局: 在@Injectable()中定义 - providedIn属性
  2. 模块级: 在@NgModule()中定义 - providers属性
  3. 组件级: 在@Component()中定义 - providers属性

定义使用全局Provider

全局的Provider, 也叫根(root)注入器,定义的方式就是在Injectable中声明providedIn属性为root,就像我们上面的例子一样,

1
2
3
4
5
6
@Injectable({
providedIn: 'root',
})
export class BackendService {
...
}

在整个应用中,只有一个root注入器(Injector), 声明为root,或者在AppModule中使用providers进行注册,效果是一样的。而且在root注册器中,每个类只会产生一个实例(单例模式)。

定义使用模块级Provider

1
2
3
4
5
@NgModule {
...
providers: [BackendService]
...
}

在模块级中声明的Provider,只对本模块中的组件产生作用。

定义使用组件级Provider

1
2
3
4
5
@Component {
...
providers: [BackendService]
...
}

在组件级别中声明的Provider, 只对当前组件或是组件树下的子组件其作用。

本文标题:如何理解Angular框架依赖注入

文章作者:晨星

发布时间:2019年05月30日 - 10:05

最后更新:2020年09月16日 - 08:09

原始链接:https://www.mls-tech.info/web/angular/angular-di-inject/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。