您的位置:首页 > Web前端 > AngularJS

Angular2 依赖注入

2017-04-26 15:43 316 查看

依赖注入介绍

控制反转概念最早在2004年由Martin Fowler提出,是针对面向对象设计不断复杂化而提出的一种设计原则,是一种利用面向对象编程法则来降低应用程序耦合的设计模式。IoC强调的对代码引用的控制权由调用方法转移到外部容器,在运行时通过某种方式注入进来,实现控制的反转,这大大降低了服务类之间的耦合度。依赖注入是一种最常用的实现IoC的方式。

在依赖注入模式中,应用组件无需关注所依赖对象的创建和初始化过程,可以认为框架已初始化好了,开发者只管调用即可。依赖注入有利于应用程序中各模块之间的解耦,使得代码更容易维护。这种优势可能一开始体现不出来,但随着项目复杂度的增加,各模块、组件、第三方服务等相互调用更频繁时,依赖注入的优点就体现出来了。开发者可以专注于所依赖对象的消费,无需关注这些依赖对象的产生过程,这将大大提升开发效率。

接下来通过一个机器人的例子来加深理解依赖注入的好处

//不使用依赖注入的实例
export class Head{

}
export class Arms{

}
export class Robot{
public head:Head;
public arms:Arms;
constructor(){
this.head=new Head();
this.arms=new Arms();
}
move(){

}

}


一个Robot类会包含Head、Arms、Feet等多各组件。此时,上面的代码存在哪些问题呢?

扩展性差

Robot类通过Head和Arms创建了自己需要的组件,即头和胳膊。如果Head类的构造函数需要一个参数呢,此时只能通过this.haed=new Head(theNewParam)的方式修改Robot类

难以测试

当需要测试Robot类时,需要考虑Robot类隐藏的其他依赖。比如Head组件本身是否依赖于其他组件,且它依赖的组件是否也依赖于另一个组件,另外Head组件的实例是否发送了异步请求到服务器。正是因为不能控制Robot的隐藏依赖,所以Robot很难被测试。

//使用依赖注入实例
export class Head{

}
export class Arms{

}
export class Robot{
public head:Head;
public arms:Arms;
constructor(public head1:Head,public arms1:Arms){
this.head=head1;
this.arms=arms1;
}
move(){

}

}


这里把依赖的对象作为参数传给构造函数,Robot类中不再创建Head和Arms。当创建Robot类实例时,只需要把创建好的Head和Arms实例传给它的构造函数即可。

var robot=new Robot(new Head(),new Arms());


到此,就实现了Robot类与Head类及Arms类的解耦,开发者可以注入任何Head和Arms实例到Robot类的构造函数。

依赖注入通过注入服务方式替代了在组件里初始化所以来的对象,从而避免了组件之间的紧耦合。但是这还不够,在使用Robot类时需要手动创建Head和Arms类,为了减少重复操作,可以通过创建一个工厂类来解决。

import {Robot, Head, Arms} from "./Robot";

export class RobotFactory{
createRobot(){
let robot=new Robot(this.createHead(),this.createArms());
}

createHead(){
return new Head();
}
createArms(){
return new Arms();
}
}


上面代码只有三个方法,比较好维护,但是随着代码量的增加,维护这些代码就会变得很棘手。幸运的是,Angular的依赖注入框架替开发者解决了这个问题。有了它,开发者就不用去关心需要定义哪些依赖,以及把这个依赖注入给谁,因为依赖注入提供了注入器,它会帮开发者创建需要的类的实例。

Angular依赖注入

概述

首先介绍几个简单的概念。

注入器(Inject):就像制造工厂,提供了一些列的接口用于创建依赖对象的实例

Provider:用于配置注入器,注入器通过它来创建被依赖对象的实例,Provider把标识映射到工厂方法中,被依赖的对象就是通过该方法创建的。

依赖(Dependence):指定了被依赖对象的类型,注入器会根据此类型创建对应的对象。

在组件中注入服务

Angular在底层做了大量的初始化工作,这大大简化了创建依赖注入的过程,在组件中使用依赖注入需要完成以下三个步骤

- 通过import导入被依赖对象的服务

- 在组建中配置注入器。在启动组件时,Angular会读取@Component装饰器里的providers元数据,它是一个数组,配置了该组件需要使用到的所有依赖,Angular的依赖注入框架就会根据这个列表去创建对应对象的实例。

- 在组件构造函数中声明所注入的依赖。注入器就会根据构造函数上的声明,在组件初始化时通过第二步中的providers元数据配置依赖,为构造函数提供对应的依赖服务,最终完成注入过程。

import {Component, OnInit} from '@angular/core';
import {ContactService} from '../shared/contact.service';

@Component({
selector: 'call-record',
templateUrl: './collection.component.html',
styleUrls: ['./collection.component.css'],
providers:[ContactService]
})
export class CollectionComponent implements OnInit {
collections:any = [];
contacts:any = {};

constructor(private _constactService: ContactService) { }
}


在服务中注入服务

除了组件服务依赖,服务间的相互调用也很常见。

//logger.service.ts
import {Injectable} from "@angular/core";
@Injectable()
export class LoggerService{
lo
4000
g(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]


在上述中,LoggerService和ContactService这两个服务都用了@Injectable()装饰器,实际上它并不是必须的,只有一个服务依赖其他服务时,才需要用@Injectable()显示装饰。上述的LoggerService服务并没有依赖其他服务,它可以不用@Injectable()装饰,而ContactService服务依赖了其他服务,则需要@Injectable()装饰。

Angular官方推荐无论是否有依赖其他服务,都应该使用@Injectable()来撞死服务。一方面,开发者在给某个组件注入其他服务时,无需再确认该服务是否添加了@Injectable();另一方面,这也是一种良好的团队协作方式,整个团队遵循相同的开发原则。

在模块中注入服务

在根组件中注入这个服务,所有子组件都能共享这个服务。

在模块中注入服务和之前的注入场景稍有不同。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服务都是独立的,如果把注入器提升到父组件中,则结果将会不一样。

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;
}
}


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会先从当前组件的注入器中查找,找不到就继续往父组件的注入器查找,直到根组件注入器,最后到应用根注入器,此时找不到的话就会报错。

注入到派生组件

一个组件可以派生与另一个组件,对于有继承关系的组件,当父类组件和派生类组件有相同的依赖注入时,如果父类组件注入了这些依赖,派生组件也需要注入这些相同的依赖,并在派生类组件的构造函数中通过super()往上传递。

/**
* Created by Administrator on 2017/4/26.
*/
import {Component, OnInit} from "@angular/core";
import {ContactService} from "./contact.service";
@Component({
selector:'contact-app',
providers:[ContactService],
templateUrl:'./app/contact-app.html'
})
export class ContactAppComponent implements OnInit{
collections:any={};
constructor(protected _contatcService:ContactService){}
ngOnInit(): void {
this._contatcService.getCollections().subscribe(data=>{
this.collections=data;
this.afterGetContacts();
});
}
protected afterGetContacts(){}
}


/**
* Created by Administrator on 2017/4/26.
*/
import {Component, OnInit} from "@angular/core";
import {ContactService} from "./contact.service";
import {ContactAppComponent} from "./contactApp.component";
@Component({
selector:'contact-app',
providers:[ContactService],
templateUrl:'./app/contact-app.html'
})
export class SortedContactComponent extends ContactAppComponent{
protected afterGetContacts() {
this.collections=this.collections.sort((h1,h2)=>{
return h1.name<h2.name?-1:(h1.name>h2.name?1:0);
})
}
constructor(protected _contatcService:ContactService){
super(_contatcService);
}

}


限定方式的依赖注入

到目前为止,注入都是假定依赖对象存在的,然而实际情况往往并非如此,比如,上层提供的Provider被移除,导致之前注入的依赖可能已经不存在了,此时,再按照前面讲的依赖注入方式进行相关服务的调用,应用就会出错。Angular依赖注入框架提供了@Optional和@Host装饰器来解决上面提到的问题。Angular的限定注入方式使得开发者能够修改默认的额依赖查找规则,@Optional可以兼容依赖不存在的情况,提高系统的健壮性。@Host可以限定查找规则,明确实例初始化位置,避免一些莫名的共享对象问题。

在Angular中实现可选注入很简单,在宿主组件的构造函数中增加@Optional()装饰器即可

import {Injectable, Optional} from "@angula
9e1e
r/core";
import {LoggerService} from "./logger.service";
@Injectable()
export class ContactService{
constructor(@Optional()private _logger:LoggerService){
if(this._logger){
this._logger.log("ContactService")
}
}
getCollections(){
this._logger.log('获取联系人...')
}
}


依赖查找的规则是按照注入器从当前组件向父组件查找,直到找到要注入的依赖为止。但有时候想限制默认的查找规则,@Host 装饰器将把往上搜索的行为截止在宿主组件。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  依赖注入 angular2