一、什么是依赖注入
控制反转 Inversion of Control (简称IOC) 最早在2004年由Martin Fowler提出,是针对面向对象设计不断复杂化而提出的一种设计原则,是一种利用面向对象编程法则来降低应用程序耦合的设计模式。IoC强调的对代码引用的控制权由调用方法转移到外部容器,在运行时通过某种方式注入进来,实现控制的反转,这大大降低了服务类之间的耦合度。依赖注入是一种最常用的实现IoC的方式。
实现IOC的框架叫IOC容器,Angular实现控制反转的手段就是依赖注入
依赖注入 Dependency Injection(简称DI)是用来创建对象及其依赖的其它对象的一种方式。 当依赖注入系统创建某个对象实例时,会负责提供该对象所依赖的对象(称为该对象的依赖)。在依赖注入模式中,应用组件无需关注所依赖对象的创建和初始化过程,可以认为框架已初始化好了,开发者只管调用即可。依赖注入有利于应用程序中各模块之间的解耦,使得代码更容易维护。这种优势可能一开始体现不出来,但随着项目复杂度的增加,各模块、组件、第三方服务等相互调用更频繁时,依赖注入的优点就体现出来了。开发者可以专注于所依赖对象的消费,无需关注这些依赖对象的产生过程,这将大大提升开发效率。
二、使用依赖注入的好处
1.松耦合和可重用性
2.提高可测试性
RealLoginService在其他小组开发完后才进行替换,之前的开发引用MockLoginService的静态代码进行测试。
三、Angular中如何实现依赖注入
1.注入器(Inject):
每一个组件都有一个注入器实例,负责注入组件需要的对象,注入器是Angular提供的一个服务类,一般情况下不需要直接调用注入器的方法,注入器会自动通过组件的构造函数将组件所需的对象注入进组件。
constructor(private productService:ProductService){...}
此组件的的构造函数声明了一个productService属性,他的类型是ProductService
Angular的注入器在看到这样一个构造函数声明时,其会在整个Angular应用中去寻找ProductService的实例,如果能找到这个实例,那么就会将ProductService注入到productService里去。
2.提供器(Provider):
用于配置注入器,注入器通过它来创建被依赖对象的实例,Provider把标识映射到工厂方法中,被依赖的对象就是通过该方法创建的。
providers:[ProductService]
等同于 providers:[{provide:ProductService, useClass:ProductService}]
provide指定了提供器的token, useClass表示实例话属性为new。
providers:[{provider:ProductService, useClass:AnotherProductService}]
providers:[{provide:ProductService, useFactory:()=>{...}]
3.提供器的作用域规则
- 当一个提供器声名在一个模块中时,所有组件都可注入。
- 当一个提供器声名在一个组件时,只对此组件和其子组件可见,其他组件不可注入。
- 当声明在模块中的提供器和声明在组件的提供器具有相同的token时,声明在组件的提供器会覆盖声明在模块中的提供器。
- 一般情况下我们会优先将服务提供器声明在模块中,只有在服务必须于某个组件相对于其他组件不可见时才声明在组件中,而这种情况十分少见。
四、在模块中注入服务
在根组件中注入这个服务,所有子组件都能共享这个服务。
在模块中注入服务和之前的注入场景稍有不同。Angular在启动程序时会启动一个根模块,并加载它所依赖的其他模块,此时会生成一个全局的根注入器,由该注入器创建的依赖注入对象在整个应用程序级别可见,并共享一个实例。同时根模块会指定一个根组件并启动,由该根组件添加的依赖注入对象是组件树级别可见,在根组件以及子组件中共享一个实例。
import {NgModule} from '@angular/core'
import {RouterModule} from "@angular/router";
import {FormsModule} from "@angular/forms";
import {BrowserModule} from "@angular/platform-browser";
import {HttpModule} from "@angular/http";
import {rootRouterConfig} from "./app.routes";
import {AppComponent} from "./app.component";
import {ContactService, UtilService} from "./shared";
@NgModule({
declarations: [
AppComponent
],
imports : [BrowserModule, FormsModule, HttpModule, RouterModule.forRoot(rootRouterConfig)],
providers : [ContactService, UtilService],
bootstrap : [AppComponent]
})
export class AppModule {
}
五、层级注入
Angular以组件为基础,项目开发中自然会有层级嵌套的情况,这种组织关系组成了组件树。根组件下面的各层级的子组件,可以出现在任何层级的任何组件中,每个组件可以拥有一个或多个依赖对象的注入,每个依赖对象对于注入器而言都是单例。
//生成唯一标识服务
import {Injectable} from "@angular/core";
@Injectable()
export class Random{
public num;
constructor(){
this.num=Math.random();
}
}
//子组件A
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
selector:'contact-a',
providers:[Random],
template:`<div>ContactA:{{random.num}}</div>`
})
export class contactAComponent{
random:Random;
constructor(r:Random){
this.random=r;
}
}
//子组件B
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
selector:'contact-b',
providers:[Random],
template:`<div>ContactB:{{random.num}}</div>`
})
export class contactBComponent{
random:Random;
constructor(r:Random){
this.random=r;
}
}
//父组件
import {Component} from "@angular/core";
@Component({
selector:'contact-list',
template:`
<h1>Contact-List</h1>
<contact-a></contact-a>
<contact-b></contact-b>
`
})
export class ContactListComponent{
constructor(){}
}
结果将输出:
Contact-List
ContactA:0.4500488165839276
ContactB:0.5389674473022938
上述的结果说明,每个子组件都创建了自己独立的注入器,也就是说通过依赖注入的Random服务都是独立的,如果把注入器提升到父组件中,则结果将会不一样。
//子组件A
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
selector:'contact-a',
//providers:[Random],
template:`<div>ContactA:{{random.num}}</div>`
})
export class contactAComponent{
random:Random;
constructor(r:Random){
this.random=r;
}
}
//子组件B
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
selector:'contact-b',
//providers:[Random],
template:`<div>ContactB:{{random.num}}</div>`
})
export class contactBComponent{
random:Random;
constructor(r:Random){
this.random=r;
}
}
//父组件
import {Component} from "@angular/core";
import {Random} from "./Random";
@Component({
selector:'contact-list',
providers:[Random],
template:`
<h1>Contact-List</h1>
<contact-a></contact-a>
<contact-b></contact-b>
`
})
export class ContactListComponent{
constructor(){}
}
此时,结果变为
Contact-List
ContactA:0.6257492668005642
ContactB:0.6257492668005642
上述的输出结果说明了子组件继承了父组件的注入器,所以子组件使用了相同的Random实例,输出了相同的结果。
那么,该如何选择在根组件还是在子组件中注入服务呢?
这取决于想让注入的依赖服务具有局限性还是全局性,由于每个注入器总是将它提供的服务维持单例,因此,如果不需要针对每个组件都提供独立的服务单例,就可以在根组件中注入,整个组件树共享根注入器提供的服务实例;如果需要针对每个组件提供不同的服务实例,就应该在格子组件中配置providers元数据来注入服务。
Angular如何查找到合适的服务实例呢?
在组件的构造函数视图注入某个服务的时候,Angular会先从当前组件的注入器中查找,找不到就继续往父组件的注入器查找,直到根组件注入器,最后到应用根注入器,此时找不到的话就会报错。
六、使用工厂函数
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {AppComponent} from './app.component';
import {Product1Component} from './product1/product1.component';
import {ProductService} from './shared/product.service';
import {Product2Component} from './product2/product2.component';
import {LoggerService} from './shared/logger.service';
import {AnotherProductService} from './shared/another-product.service';
@NgModule({
declarations: [
AppComponent,
Product1Component,
Product2Component
],
imports: [
BrowserModule
],
providers: [
{
provide: ProductService,
useFactory: () => {
let logger = new LoggerService();
let dev = Math.random() > 0.5;
if (dev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
}
}
, LoggerService],
bootstrap: [AppComponent]
})
export class AppModule {
}
这样我们根据生成随机数的不同而调用不同的服务,注意Product1Component和Product2Component两个组件使用的服务永远是相同的,这意味着工厂方法创建的对象是一个单例对像,工厂方法只在创建第一个需要被注入的对象时调用一次,然后在整个应用中所有被注入的ProductService的实例都是同一个对象。
现在有两个问题,一是我们手动实例化ProductService,这样会造成我们的工厂方法会跟LoggerService紧密的耦合在一起,而实际上我们是有声明LoggerService这个提供器的,接下来我们将在工厂方法里使用使用LoggerService提供器。
@NgModule({
declarations: [
AppComponent,
Product1Component,
Product2Component
],
imports: [
BrowserModule
],
providers: [
{
provide: ProductService,
useFactory: (logger: LoggerService) => {
let dev = Math.random() > 0.5;
if (dev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
},
deps: [LoggerService]
}
, LoggerService],
bootstrap: [AppComponent]
})
export class AppModule {
}
使用deps声明工厂方法需要的参数。
第二个问题,在我们的例子中实例话哪个对象是由一个随机数决定的,在真实情况中我们可能会通过某个变量来控制。这时我们声明一个新的提供器
@NgModule({
declarations: [
AppComponent,
Product1Component,
Product2Component
],
imports: [
BrowserModule
],
providers: [
{
provide: ProductService,
useFactory: (logger: LoggerService, isDev) => {
if (isDev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
},
deps: [LoggerService, 'IS_DEV_ENV']
}
, LoggerService,
{
provide: 'IS_DEV_ENV', useValue: false
}],
bootstrap: [AppComponent]
})
export class AppModule {
}
也可以使用值对象
@NgModule({
declarations: [
AppComponent,
Product1Component,
Product2Component
],
imports: [
BrowserModule
],
providers: [
{
provide: ProductService,
useFactory: (logger: LoggerService, appConfig) => {
if (appConfig.isDev) {
return new ProductService(logger);
} else {
return new AnotherProductService(logger);
}
},
deps: [LoggerService, 'APP_CONFIG']
}
, LoggerService,
{
provide: 'APP_CONFIG', useValue: {isDev:false}
}],
bootstrap: [AppComponent]
})
export class AppModule {
}
七、在服务中注入服务
//logger.service.ts
import {Injectable} from "@angular/core";
@Injectable()
export class LoggerService{
log(message:string){
console.log(message);
}
}
//contact.service.ts
import {Injectable} from "@angular/core";
import {LoggerService} from "./logger.service";
@Injectable()//添加装饰器@Injectable()
export class ContactService{
//构造函数中注入所依赖服务
constructor(private _logger:LoggerService){}
getCollections(){
this._logger.log('获取联系人...')
}
}
//在组件的providers元数据中注册服务
providers:[LoggerService,ContactService]
- 只有声明了@Injectable()这个装饰器的服务才可以被注入其他服务。
- 组件中没有@Injectable()也可在构造函数中注入服务的原因是组件中@Component是@Injectable()的子类。