0%

angular-dependency-injection

Dependency injection(DI) - 是Angular的核心概念之一 is a design pattern that allows us to remove the hard-coded dependencies and make our application loosely coupled, maintainable, and testable. It is a way to achieve Inversion of Control (IoC) in our application.

DI is widely used in Angular, React, and other modern frameworks. In Angular, we can inject services, components, and other dependencies into our components, services, and directives.

Some terms:

  • Dependency 依赖
  • Injector 注入器
  • Registry 注册中心
  • DI - Dependency Injection
  • IoC - Inversion of Control

Ways to implement DI:

  • Constructor Injection
  • Setter Injection
  • Interface Injection

Angular Dependency Injection

Angular通过一个叫做Injector(注入器)的服务来实现DI。注入器相当于依赖的生产者和依赖的消费者之间的桥梁。Angular会在启动时创建一个应用级别的注入器(也叫root injector),当然也会创建其他的injector(如果需要的话)。

当需要一个依赖时,Angular首先会检查其注册中心是否有这个依赖,如果有则直接返回,如果没有则会根据依赖的类型创建一个实例并返回。同时将其加入到注册中心中,以便下次使用。

在依赖注入中有两个主要的角色,一个是依赖的生产者,比如用来获取后台数据的Service,一个是依赖的消费者,比如使用这个ServiceComponent。而Injector就是这两者之间的桥梁。有了Injector,依赖生产者和依赖消费者之间便实现了解耦。

Inversion of control

控制反转 - 到底怎么个反转呢?
在没有依赖注入之前,对象自己管理自己的依赖。

1
2
3
4
5
6
class Car {
private engine: Engine;
constructor() {
this.engine = new Engine(); // 直接创建依赖
}
}

有了依赖注入之后,对象不在负责依赖的管理,而是交给Angular框架来管理,把管理依赖的职责由对象转移到外层框架,依赖的查找、注入都由框架负责,这就是控制反转。

1
2
3
4
5
6
7
8
@Component({
selector: 'app-car',
template: '...',
providers: [Engine] // 依赖配置在外部, 如果依赖有{provideIn: 'root'},则不需要配置这里
})
class CarComponent {
constructor(private engine: Engine) {} // 依赖由 Angular 注入
}

Provide a dependency

在Angular中,有三种方式提供一个依赖。

provideIn

创建依赖

1
2
3
4
@Injectable({
providedIn: 'root'
})
class HeroService {}

使用依赖

1
constructor(private heroService: HeroService) {}

使用provideIn:'root'方式提供的依赖有如下特点:

  1. 该依赖是全局共享的的,只有一个实例。
  2. 所有组件、指令、服务等都可以使用该依赖。
  3. 支持tree shaking, 如果该依赖没有被使用,则不会被打包到最终的bundle中。

providers

创建依赖

1
2
@Injectable() // <--- 注意这里没有provideIn
class HeroService {}

使用依赖

1
2
3
4
@NgModule({
providers: [HeroService]
})
class AppModule {}

使用providers方式提供的依赖有如下特点:

  1. 每个使用该依赖的模块对应的实例都会创建一个该依赖的实例。
  2. 该依赖是局部的,只有在提供了providers的模块中才能使用。
  3. 不支持tree shaking, 无论是否使用,都会被打包到最终的bundle中。
  4. 此依赖实例的生命周期和组件相同,当组件销毁时,该依赖实例也销毁。

注意,如果当前组件引入了一个依赖,比如一个Service,那么这个依赖:

  • 在当前模块可用
  • 在当前模块的模板中的所有组件中也可用

举个例子:当前组件是ProductComponent,其模板中引用了ProductDetailComponent。ProductComponent引入了一个ProductService,那么:

ApplicationConfig

创建依赖

1
2
@Injectable() // <--- 注意这里没有provideIn
class HeroService {}

使用依赖

1
2
3
4
5
6
export const appConfig: ApplicationConfig = {
providers: [
{ provide: HeroService },
]
};
bootstrapApplication(AppComponent, appConfig)

使用ApplicationConfig方式提供的依赖有如下特点:

  1. 该依赖是全局共享的的,只有一个实例。
  2. 所有组件、指令、服务等都可以使用该依赖。
  3. 不支持tree shaking, 无论是否使用,都会被打包到最终的bundle中。

此方式与providedIn: 'root'颇为相似,不同的是这种方式不支持tree shaking。

NgModule based configuration

创建依赖

1
2
@Injectable() // <--- 注意这里没有provideIn
class HeroService {}

使用依赖

1
2
3
4
@NgModule({
providers: [HeroService]
})
class AppModule {}

A service provided in a module is available to all declarations of the module, or to any other modules which share the same ModuleInjector. To understand all edge-cases, see Hierarchical injectors.

Pros of DI:

  • Loose coupling
  • Easy to test
  • Easy to maintain

references: