之前只知道依赖注入是Angular中的一个特性,对于依赖注入有一个大概的了解,但是并没有仔细查询过依赖注入,这里记录一下对依赖注入的重新学习。
什么是依赖注入:
当你开发系统的某个较小部件时(例如模块或类),你可能需要使用来自其他类的特性。例如,你可能需要 HTTP 服务来进行后端调用。
依赖注入或 DI 是一种设计模式和机制,用于创建应用程序的某些部分并将其传递到需要它们的应用程序的其他部分。Angular 支持这种设计模式,你可以在应用程序中使用它来提高灵活性和模块化程度。
从上面可以得出,依赖注入涉及到依赖提供者与依赖使用者,在依赖提供者注册之后就可以在系统的其他地方使用依赖了。
在Angular中,提供的依赖通常是服务,当然也可以是其他内容。在注册后,依赖会被注入到注入器内,然后就可以在系统的其他地方使用了,注入依赖项的最常见方法是在类的构造函数中声明它。当 Angular 创建组件、指令或管道类的新实例时,它会通过查看构造函数的参数类型来确定该类需要哪些服务或其他依赖项。
注入器是在应用程序启动期间自动创建的,根注入器只有根模块中一个,然后每个模块中另外有属于自己的注入器。(当然注入器也不只存在于模块)
使用中的性质
只可以在被Angular管理的类中使用
Angular DI中,允许带有 Angular 装饰器的类(例如组件、指令、管道和可注入对象)配置它们所需的依赖项。
也就是允许被Angular管理的类配置他们所需要的依赖项,而在其他的工具类中,则无法使用Angular的DI。
当有人请求依赖项时,注入器会检查其注册表以查看那里是否已有可用的实例。如果没有,就会创建一个新实例并将其存储在注册表中。
下面结合代码简单演示:
import {Injectable} from "@angular/core";
@Injectable()
export class AppService {
title = 'AppService';
}
constructor(appService: AppService) {
console.log(appService.title);
}
首先定义了一个AppService,并且在根模块的AppComponent组件中请求注入一个AppService实例,此时根注入器发现注册表中没有可用实例,就尝试使用AppService类型来创建一个新实例,但是发现在根模块中并没有提供应该如何去实例化的类,所以就会报错。此时将AppService添加到根模块的providers中即可。
作用范围
首先需要明确模块中定义的依赖提供者(providers)可以在本模块注入,也可以在其子模块中注入。
在 NgModule 级别,要使用 @NgModule 装饰器的 providers 字段。在这种情况下,AppService 可用于此 NgModule 或与本模块位于同一个 ModuleInjector 的其它模块中声明的所有组件、指令和管道。
比如,模块关系如图:
此时在B模块中注入了一个AppService,就可以在AppModule以及AModule中使用,AppModule和A、B两个模块的关系:在AppModule中import A和B两个模块。
对比引用内容也就是,AppService可以用于此NgModule(BModule),或与本模块位于同一个 ModuleInjector(RootModuleInjector) 的其他模块(AModule)中声明的所有组件(AComponent, BComponent),指令和管道。
可以理解成在根模块中引入了A和B两个模块,那么A和B两个模块中的providers都可以在根模块中使用,也就是根模块的providers = AProviders ∪ BProviders。所以在子模块中(AModule)也就可以使用根模块的依赖提供者。
摇树优化
在 @Injectable 元数据中注册提供者还允许 Angular 通过从已编译的应用程序中删除没用到的服务来优化应用程序,这个过程称为摇树优化(tree-shaking)。
这里的元数据是@Injectable中提供的数据,注册提供者之后,如果某个依赖提供者没有被使用,那么注入器会将这些依赖提供者删除来进行优化。
依赖注入配置
依赖注入需要为providers赋值,如providers: [AppService]
,可以出现在模块,服务,组件等Angular 装饰器中。
常见的方式是:providers: [AppService]
,这其实是一种简写,扩展之后就是:providers: [{provide: AppService, useClass: AppService}]
,当提供者令牌为服务类时,注入器的默认行为是使用 new 运算符实例化该类。{provide: AppService, useClass: AppService}
是Provide接口的一个实现,有关该接口的内容:
其中第一个字段provide
:属性包含一个令牌,该令牌会作为定位依赖值和配置注入器时的键。(值为"提供者令牌",可以是服务类,也可以是InjectionToken)
第二个字段的值叫做提供者定义对象,用来告诉注入器如何创建依赖值;对于他的键,可以不只是useClass
,可选项有:
- useClass
- useExisting
- useFactory
- useValue
useClass(类提供者)
这个提供者键名能让你创建并返回指定类的新实例。
用法:
providers: [{provide: AppService, useClass: AppService1}]
providers: [{provide: AppService, useClass: AppService2}]
useExisting(别名提供者)
useExisting 提供者键允许你将一个令牌映射到另一个。
即对一个实例提供两种访问方式。
比如:
providers: [
AppService
{provide: IndexService, useExisting: AppService}
]
这样在注入依赖的时候可以使用
constructor(service: AppService) {
}
constructor(service: IndexService) {
}
这两种方式,最终访问的都是一个实例。
useFactory(工厂提供者)
useFactory 提供者键允许你通过调用工厂函数来创建依赖对象。
使用这种方法,你可以根据 DI 和应用程序中其他地方的可用信息创建动态值。
可见官方示例,最终代码:
providers: [
Logger,
UserService,
{ provide: HeroService,
useFactory: heroServiceFactory,
deps: [Logger, UserService]
}
]
DI也就是指其中的deps的内容,heroServiceFactory
是一个(logger: Logger, userService: UserService) => HeroService
,其通过userService中的内容来动态的实例化HeroService。
useValue(值提供者)
useValue 键允许你将固定值与某个 DI 令牌相关联
InjectionToken 对象
可以定义和使用一个 InjectionToken 对象来为非类的依赖(函数、对象、基本类型[例如字符串或 Boolean]或任何其他类型)选择一个提供者令牌。
// 定义提供者令牌
export const APP_CONFIG = new InjectionToken('app.config');
// 依赖提供
providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]
// 依赖使用
constructor(@Inject(APP_CONFIG) config: AppConfig) {
this.title = config.title;
}
new InjectionToken
中的AppConfig表示依赖提供者的类型,'app.config'是对提供者令牌的描述,两者用来指明此令牌的用途。
这里的AppConfig是一个接口,Ts是可以使用接口作为提供者令牌的,但是由于程序在运行时使用的是js,所以没有可供 DI 框架使用的运行时表示或令牌。所以下面这种写法是不可取的:
[{ provide: AppConfig, useValue: HERO_DI_CONFIG })]
constructor(private config: AppConfig){ }