Angular 2中的依赖注入
原文地址:http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html 依赖注入一直是Angular的一个最大特点和卖点。它允许在我们应用不同组件中注入依赖,而不需要知道这些依赖是如何创建的,或者它们需要的依赖关系是什么,可是,已证明了目前的Angular 1的依赖注入系统有一些问题,所以建立了下一代框架Angular 2来解决这些问题,在这篇文章中,我们将探索新的依赖注入系统。 在我们进入新的依赖注入系统之前,先让我们先了解什么是依赖注入,而Angular 1的问题又是什么问题。 依赖注入是一种模式在ng-conf 2014中Vojta Jina有一个关于依赖注入的演讲,在这次演讲中,它讨论了在开发Angular 2中关于新的DI系统的想法和故事,他很清楚,我们可以把DI看成两件事:作为一种设计模式和作为一个框架。而前者用来解释DI模式,后者可以帮助我们的系统,维护和组装依赖关系。这篇文章中我想做同样的事来帮助我们理解这一概念。 我们首先考虑看看下面的代码。 class Car {
constructor() {
this.engine = new Engine();
this.tires = Tires.getInstance();
this.doors = app.get('doors');
}
}
在这里没什么特别的东西,我们有一个 这导致代码难以维护,甚至更难测试。想象一下你想测试这个类,在代码中你将如何替换依赖Engine为MockEngine? 当编写测试时,我们想要使用代码在不同的场景中测试,因此此每个场景需要它自己的配置。 那么如何才能更好的写出这样的代码,使它更容易测试?这是超级容易,你可能已经知道该怎么做。我们把代码改成这样: class Car {
constructor(engine,tires,doors) {
this.engine = engine;
this.tires = tires;
this.doors = doors;
}
}
所有我们做的是,我们将期望所有需要依赖的对象从构造函数创建移动到构造函数的参数创建,在这个代码中没有具体的实现,我们从字面上把创建这些依赖关系的责任转移到一个更高的层次上。如果我们想创建一个Car对象,我们所要做的就是把所有需要的依赖关系传递给构造函数: var car = new Car(
new Engine(),new Tires(),new Doors()
);
这太酷了,不是吗?依赖从我们的类脱离出来,在我们编写测试中,我们可以通过mock依赖实现测试 var car = new Car(
new MockEngine(),new MockTires(),new MockDoors()
);
这就是依赖注入。更具体一点,这个特定的模式也被称为构造函数注入。还有两个注入模式, 好酷,现在我们正在使用DI,但是什么时候来一个DI系统呢?如前所述,我们从字面上把依赖性创造的责任转移到一个更高的水平。这正是我们新的问题。谁来负责组装所有这些依赖关系?是我们。 function main() {
var engine = new Engine();
var tires = new Tires();
var doors = new Doors();
var car = new Car(engine,doors);
car.drive();
}
我们必须保留一个main函数,这样做是相当危险的,尤其是当应用程序变得越来越大,如果我们能做点像这样的事情,那是不是更好? function main() {
var injector = new Injector(...)
var car = injector.get(Car);
car.drive();
}
依赖注入作为一个框架这是依赖注入作为框架的地方。众所周知,Angular 1有它自己的DI系统,允许我们注释服务和其他组件,让injector发现他们知道什么依赖需要实例化,例如,下面的代码演示了如何在Angular 1中注释我们的Car类: class Car {
...
}
Car.$inject = ['Engine','Tires','Doors'];
然后,我们注册我们的Car作为一个服务,每当我们使用它时,我们得到一个单例,它不需要关心Car需要创建的依赖。 var app = angular.module('myApp',[]);
app.service('Car',Car);
app.service('OtherService',function (Car) {
// instance of Car available
});
这一切都很酷,但事实证明,现有的DI有一些问题:
这些问题需要得到解决,以便使Angular的DI达到下一个水平。 Angular 2中得依赖注入在看实际代码之前,让我们首先了解新的DI系统背后的概念。下面的图片说明了新的DI系统所需的组件: 在Angular 2中DI基本上是由三个东西组成的:
好吧,现在我们有一个概念,让我们看看翻译成代码是什么样子的。我们继续保持Car类的依赖关系。下面是我们如何能够利用Angular 2的DI拿到Car的一个实例: import { Injector } from 'angular2/di';
var injector = Injector.resolveAndCreate([
Car,Engine,Tires,Doors
]);
var car = injector.get(Car)
我们从Angular 2中导入Injector模块,这暴露一些静态api,Injector.resolveAndCreate()基本上是一个工厂函数用来创建一个injector,然后提供一个provider列表。我们稍后将探讨如何将class提供给provider,但现在我们将焦点关注在injector.get()方法上。在代码最后行告诉我们怎么样去获取一个Car实例,我们的inject怎么知道需要创建依赖关系来实例化一个Car,看看我们的Car类来解释下为什么… import { Inject } from 'angular2/di';
class Car {
constructor(
@Inject(Engine) engine,@Inject(Tires) tires,@Inject(Doors) doors
) {
...
}
}
我们从框架中导入了Inject的模块,用它来装饰(decorator)我们的构造函数参数。如果你不知道decorator是什么,你可能会想读我们的文章 decorators 和 annotations两者的不同和使用ES5编写Angular2代码。 Inject decorator会在我们的类上附加些元信息,之后会被DI系统给读取,所以基本上我们在这里所做的是告诉DI第一个参数是Engine类型的实例,第二个参数是Tires类型,第3个参数是Doors类型。我们可以使用TypeScript重写这些代码,感觉有点更自然: class Car {
constructor(engine: Engine,tires: Tires,doors: Doors) {
...
}
}
好的,我们的类声明它自己的依赖与DI可以读的实例信息,但Injector如何知道要如何创建这样一个对象?这就是provider的作用。记得resolveAndCreate()方法中,我们传递一个数组形式的类列表吗? var injector = Injector.resolveAndCreate([ Car,Doors ]);
同样,你可能会想知道这个类的列表应该是一个provider列表。,如果我把写得这些转换为更详细的语法,那么可能会变得有点更加清晰。 import {provide} from 'angular2/angular2';
var injector = Injector.resolveAndCreate([
provide(Car,{useClass: Car}),provide(Engine,{useClass: Engine}),provide(Tires,{useClass: Tires}),provide(Doors {useClass: Doors})
]);
我们有一个provide函数,他会映射一个token到配置的object上,token可以是一种type或者字符串,如果你现在阅读那些providers,你很容易理解发生了什么,我们绑定Car类型到Car类上,Engine类型到Engine类上等。这是我们之前所谈到的食谱机制。 现在,下一个问题来了,我们要使用更长的写法而不是简写语法吗?我们如果可以写成Foo,那就没有理由写成provide(Foo,{useClass: Foo}),对吗。这就是为什么我们开始首先使用简洁的语法。然而,较长的语法使我们能够做一些非常非常强大的事。看看下一个代码片段 provide(Engine,{useClass: OtherEngine})
对,我们可以绑定一个token到任何想要绑定的东西上,在这里绑定Engine token到OtherEngine类上,这意味着,当我们申请获取Engine类型时,我们会获取类OtherEngine的一个实例。 这是超级强大的,因为这不仅为了让我们防止名称冲突,我们也可以创建一个接口的类型并将它绑定到一个具体的实现。除此之外,我们可以在一个单一的地方不接触任何其他代码使用一个token换出它实际的依赖, Angular 2中DI的几个其他绑定方法将在下一节中探索。 其他provider配置有时候,我们不希望得到一个类的一个实例,通过更多的配置我们可以得到一个单一的值或者工厂方法,异步依赖关系也可以是我们的应用的一部分,这就是为什么Angular 2的DI的provider机制带有不止一个方法。让我们快速浏览一下它们。 值我们想要简单的绑定到值可以使用 {useValue: value} provide(String,{useValue: 'Hello World'})
当我们要绑定到简单的配置值时,这就很方便了。 别名我们可以给一个token绑定一个别名token provide(Engine,{useClass: Engine})
provide(V8,{useExisting: Engine})
工厂是的,我们最喜爱的工厂。 provide(Engine,{useFactory: () => { return function () { if (IS_V8) { return new V8Engine(); } else { return new V6Engine(); } } }})
当然,工厂可能有它自己的依赖关系。通过对工厂的依赖性很容易给工厂添加一个tokens列表: provide(Engine,{ useFactory: (car,engine) => { },deps: [Car,Engine]
})
可选依赖 该 class Car {
constructor(@Optional(jQuery) $) {
if (!$) {
// set up fallback
}
}
}
正如你所看到的,Angular 2 DI解决了Angular 1 DI的前3个问题。但还有一件事,我们还没有谈到呢。新的DI是否还是创建单例对象 短暂(Transient)的依赖和子Injector如果我们想要一个短暂的依赖,每一次获取依赖都创建一个新的实例,我们有2个选择: 工厂会返回一个类的实例,这将不会是单例. provide(Engine,{useFactory: () => { return () => { return new Engine(); } }})
我们也可以使用 var injector = Injector.resolveAndCreateChild([Engine]);
var childInjector = Injector.resolveAndCreateChild([Engine]);
injector.get(Engine) !== childInjector.get(Engine);
child injectors 也更加有趣。如果原来child injector上没有给定binding,他会查找绑定在parent injector上的token来进行绑定 图片显示了3个 我们甚至可以配置可见性的依赖关系,这将在另一篇文章中讲到Host and Visibility in Angular 2’s Dependency Injection, 在Angular 2中如何使用?现在我们已经学习了DI如果在Angular 2运作,你可能会想知道它是如何使用在框架本身,我们在建立Angular 2组件时,是否需要手动创建injector? 让我们来来下面这个简单的Angular 2组件 @Component({
selector: 'app',
template: '<h1>Hello !</h1>'
})
class App {
constructor() {
this.name = 'World';
}
}
bootstrap(App);
class NameService {
constructor() {
this.name = 'Pascal';
}
getName() {
return this.name;
}
}
现在为了在我们的应用中使用NameService,我们需要通过在应用injector中提供provider配置,但是我们该如何做?我们还没有创建一个injector。
bootstrap(App,[NameService]);
就是这样。现在我们在应用中使用 class App { constructor(@Inject(NameService) NameService) { this.name = NameService.getName(); } }
或者 使用typescript,我们可以使用参数类型来注入 class App { constructor(NameService: NameService) { this.name = NameService.getName(); } }
真棒,一下子,我们再也没有任何 比方说,我们有一个 @Component({
selector: 'app',providers: [NameService]
})
@View({
template: '<h1>Hello !</h1>'
})
class App {
...
}
为了把事情说清楚: 转自http://zai.io/topic/detail/news/b419aa2564030bd9(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |