Angular2 Dependency Injection
前言依赖注入是Angular的核心概念之一。通过依赖注入,我们可以将复杂、繁琐的对象管理工作交给Angular,将我们的工作重心更好的放在业务上。 依赖注入是一种设计模式面向对象编程,我们以类为单位组织我们的代码。举个简单的例子,例如某一款汽车,有引擎、轮胎、车门等配置,抽象成代码就是这样的 class Car { constructor() { this.engine = new Engine(); this.tires = new Tires(); this.doors = new Doors(); } } 在构造汽车的过程中,我们安装引擎、轮胎和车门等配置,这样就可以造出一辆汽车了。但是现在我们还想造同一款车,但是想换一种引擎,怎么办?很明显,上面的Car是整个封闭的,如果想换一个引擎,我们就得重新造一款车 class OtherCar { constructor() { this.engine = new OtherEngine(); this.tires = new Tires(); this.doors = new Doors(); } } 相信大家已经发现上面代码的问题了,耦合性太强,无法定制我们的引擎、轮胎和车门,要想定制,就得从头来过。如果我们的所有的引擎都符合某一个标准尺寸,然后在车里预留出这个空间,那么我们不就可以随意更换引擎了么?同理轮胎和车门,抽象成代码就是这样的 class Car { constructor(engine,tires,doors) { this.engine = engine; this.tires = tires; this.doors = doors; } } 通过组装的方式造车,预留配置的标准空间,同一款车我们可以随意使用各种配置 var car = new Car( new Engine(),new Tires(),new Doors() ); var car = new Car( new MockEngine(),new MockTires(),new MockDoors() ); 从测试的角度来说,这样的代码也是方便测试的。上面的注入方式就是 依赖注入是一种框架Angular1中我们可以使用service注入服务,就像这样 angular.module('app',[]) .controller('MyCtrl',function ($scope,comService) { comService.handle(); }) .service('comService',function () { this.handle = function () { //todo } }); 但是Angular1的依赖注入有几个问题 所有的服务全部都是单例的 var id = 1; angular.module('app',[]) .service('comService',function () { this._id = id++; this.getId = function () { return this._id; } }) .controller('ACtrl',comService) { console.log(comService.getId()); // 1 }) .controller('BCtrl',comService) { console.log(comService.getId()); // 1 }); 服务是通过名称来区分的,很容易造成冲突,后者会直接覆盖前者 angular.module('app',function () { this.name = 'company service 1'; }) .service('comService',function () { this.name = 'company service 2'; }) .controller('ACtrl',comService) { console.log(comService.name); // company service 2 }); 依赖注入功能内嵌在Angular1中,无法剥离出来单独使用 Angular2中的依赖注入组件注入服务例如有一个日志服务logger.service.ts export default class LoggerService { log(str) { console.log(`Log: ${str}`); } } 然后入口组件app.ts当中使用这个服务 import {Component} from 'angular2/core'; import LoggerService from './logger.service'; @Component({ selector: 'my-app',template: '<h1>App Component</h1>',providers:[LoggerService] }) export class AppComponent { loggerService:LoggerService; constructor(loggerService:LoggerService) { this.loggerService = loggerService; } ngOnInit(){ this.loggerService.log('component init'); } } 首先我们需要在组件的 @Component({ selector: 'my-app',providers:[LoggerService] }) export class AppComponent { constructor(private loggerService:LoggerService) {} ngOnInit(){ this.loggerService.log('component init'); } }
在Angular2组件当中使用依赖注入可以简单的分为两步
子组件注入服务新建一个uuid.ts的服务,可以生成一个唯一的ID var id = 1; export default class UuidService { id:number; constructor() { this.id = id++; } getId() { return this.id; } } 入口组件app.ts import {Component} from 'angular2/core'; import UuidService from './uuid.service'; import ChildComponent from './child'; @Component({ selector: 'my-app',template: '<h1>App Component</h1><my-child></my-child>',providers:[UuidService],directives:[ChildComponent] }) export class AppComponent { constructor(private uuidService:UuidService) {} ngOnInit(){ console.log(this.uuidService.getId()); } } 新建一个子组件child.ts import {Component} from 'angular2/core'; import UuidService from './uuid.service'; @Component({ selector: 'my-child',template: '<p>Child Component</p>' }) export default class ChildComponent { constructor(private uuidService:UuidService) {} ngOnInit(){ console.log(this.uuidService.getId()) } } 在子组件当中我们并没有配置 import {Component} from 'angular2/core'; import UuidService from './uuid.service'; @Component({ selector: 'my-child',template: '<p>Child Component</p>',providers:[UuidService] }) export default class ChildComponent { constructor(private uuidService:UuidService) {} ngOnInit(){ console.log(this.uuidService.getId()) } } 打开控制台,发现打印了 服务注入服务有时候服务之间也会相互依赖,例如上面的例子当中LoggerService依赖另一个FormatService format.service.ts export default class FormatService { format() { return 'Log: '; } } logger.service.ts import FormatService from './format.service'; export default class LoggerService { constructor(private formatService:FormatService) { } log(str) { console.log(`${this.formatService.format()}${str}`); } } app.ts import {Component} from 'angular2/core'; import LoggerService from './logger.service'; import FormatService from './format.service'; @Component({ selector: 'my-app',providers: [LoggerService,FormatService] }) export class AppComponent { constructor(private loggerService:LoggerService) { } ngOnInit() { this.loggerService.log('component init'); } }
打开控制台,发现抛出了异常,因为我们没有告知Angular2,LoggerService依赖FormatService,所以注入失败了。 logger.service.ts import FormatService from './format.service'; import {Injectable} from 'angular2/core'; @Injectable() export default class LoggerService { constructor(private formatService:FormatService) { } log(str) { console.log(`${this.formatService.format()}${str}`); } } 这样我们的程序又能正常工作了。细心的同学会发现我们的App组件也需要注入LoggerService服务,为什么不需要添加
循环依赖注入我们将上面的代码改造成下面这样 format.service.ts import LoggerService from './logger.service'; import {Injectable} from "angular2/core"; @Injectable() export default class FormatService { constructor(private loggerService:LoggerService){} format() { return 'Log: '; } } logger.service.ts import FormatService from './format.service'; import {Injectable} from "angular2/core"; @Injectable() export default class LoggerService { constructor(private formatService:FormatService) { } log(str) { console.log(`${this.formatService.format()}${str}`); } } 打开控制台会发现抛出了异常,像这种两个服务之间相互注入的情况就会产生循环依赖,我们要尽量避免这种情况的发生,保持每个服务的单一职责功能。 依赖注入核心
Angular2的依赖注入主要由三个部分构成
通过Injector的功能,我们可以脱离Angular2组件来使用依赖注入,例如上面的Car例子,首先引入 import {Injector,Injectable} from 'angular2/core'; 创建我们的Engine等类和Car类 class Engine{} class Tires{} class Doors{} @Injectable() class Car{ constructor(private engine:Engine,private tires:Tires,private dorrs:Doors){} }
调用Injector的 var injector = Injector.resolveAndCreate([Engine,Tires,Doors,Car]);
调用 var car = injector.get(Car); 比较下面的例子 injector.get(Tires) === injector.get(Tires); //true car.engine === injecotr.get(Engine); //true
Token我们知道Angular1当中注入的识别是通过参数的字符名称,例如 angular.module('app',function () { }) .controller('ACtrl',function (comService) { }); controller当中使用的service名称必须和注册处保持一致,否则注入失败。Angular2获取实例则是通过Token var injector = Injector.resolveAndCreate([Engine]); 这种方式实际上是简写的,Angular2会帮我们封装成下面的形式 var injecotr = Injector.resolveAndCreate([provide(Engine,{useClass:Engine})]);
var injector = Injector.resolveAndCreate([provide('engine',{useClass: Engine})]); var engine = injector.get('engine'); console.log(engine instanceof Engine); //true
useClass实例化类的方式注入,注入器会帮我们new实例,如果传递一个非类,typescript编译都通不过 useValue直接注入这个值 var injector = Injector.resolveAndCreate([ provide(Engine,{useValue: 'engine'}) ]); console.log(injector.get(Engine) === 'engine'); //true useFactory注入工厂方法的返回值 var injector = Injector.resolveAndCreate([provide(Engine,{ useFactory: function () { return 'engine' } })]); console.log(injector.get(Engine) === 'engine'); factory方法当中可以依赖别的服务 var injector = Injector.resolveAndCreate([EngineA,EngineB,provide(Engine,{ useFactory: function (engineA,engineB) { if (true) { return engineA; } else { return engineB; } },deps: [EngineA,EngineB] })]); console.log(injector.get(Engine) instanceof EngineA); //true useExisting使用已存在的实例注入,这个容易跟useClass弄混,注意下面的输出 var injector = Injector.resolveAndCreate([ EngineA,provide(EngineB,{useClass: EngineA}) ]); console.log(injector.get(EngineA) === injector.get(EngineB)); //false var injector = Injector.resolveAndCreate([ EngineA,{useExisting: EngineA}) ]); console.log(injector.get(EngineA) === injector.get(EngineB)); //true multi如果我们重复注册同一个Token,后面的会覆盖前面的,例如 var injector = Injector.resolveAndCreate([ provide('COM_ID',{useValue: 1}),provide('COM_ID',{useValue: 2}) ]); console.log(injector.get('COM_ID')); // 2 使用multi配置可以使相同的Token共存,注入的是一个数组 var injector = Injector.resolveAndCreate([ provide('COM_ID',{ useValue: 1,multi: true }),{ useValue: 2,multi: true }) ]); console.log(injector.get('COM_ID')); // [1,2] 相同的Token,不能出现混合的情况,例如下面的写法就会报错 var injector = Injector.resolveAndCreate([ provide('COM_ID',{useValue: 1,multi: true}),{useValue: 2}) ]); 子注入器通过 var injector = Injector.resolveAndCreate([Engine,Car]); var childInjector = injector.resolveAndCreateChild([Engine,Car]); var grantInjector = childInjector.resolveAndCreateChild([Car]); grantInjector.get(Car) === childInjector.get(Car); //false grantInjector.get(Car) === injector.get(Car); //false grantInjector.get(Engine) === childInjector.get(Engine); //true childInjector.get(Engine) === injector.get(Engine); //false grantInjector.get(Tires) === childInjector.get(Tires); //true childInjector.get(Tires) === injector.get(Tires); //true
小结自此Angular2解决了Angular1遗留的问题
原文 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |