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 装饰器将把往上搜索的行为截止在宿主组件。
相关文章推荐
- 原创:Javascript DI!Angular依赖注入的实现原理
- (七)理解angular中的module和injector,即依赖注入
- Angular-依赖注入 显式注入和隐式注入
- Angular 理解module和injector,即依赖注入
- 理解angular中的module和injector,即依赖注入
- angular 依赖注入
- angular依赖注入和路由
- angular-如何实现注入依赖
- Angular学习笔记(六)之依赖注入入门
- Angular 2中的依赖注入
- 【Angular】——依赖注入
- AngularJS $injector 依赖注入详解
- Angular---【依赖注入】
- Angular自定义依赖注入(factory方法)
- angular2依赖注入概述
- Angular之依赖注入(injector)与原生View组件
- angularjs MVC、模块化、依赖注入详解
- (七)理解angular中的module和injector,即依赖注入
- angular学习-依赖注入
- AngularJS ng依赖注入的三种方式