前端之Angular2实战:依赖注入详解与应用

Dependence Injection(依赖注入)

dependency-injection-in-angular-2javascript

依赖注入是Angular中的最大的一个特性与卖点。它容许应用中不一样的组件不须要显性地创建关联便可以相互调用。不一样,Angular 1中的依赖注入仍然是存在着一些问题,这也是Angular 2彻底重构了一套依赖注入系统的缘由。Angular 1中的依赖注入系统主要存在的问题以下:html

  • Internal Cache(内建缓存):依赖通常是被当作单例对待,任何一个服务在整个应用的生命周期中都应该只被建立一次。java

  • Synchronous by default(默认异步):Angular 1中的服务建立不是异步建立的。typescript

  • Namespace collision(命名空间冲突):在应用的生命周期中某个”type”的token是惟一的,若是咱们自定义了一个名为Car的服务,而引入的第三方框架中也存在着同名的Car的服务,那么整个系统就会存在问题。bootstrap

  • Built into the framework(框架内建):Angular 1的依赖注入是内建的,咱们没法单独的进行使用。缓存

Angular 2的依赖注入体系大概以下所示:angular2

DI in Angular 2

其中有几个关键的概念解释以下:app

  • Injector(注入器):Injector就相似于Spring里面的ApplicationContext,提供了一系列的接口以供依赖实例的建立。框架

  • Binding(绑定):Binding的做用在于告诉Injector如何去为某个依赖建立实例。一个Binding须要映射到一个工厂类型的方法。异步

  • Dependence(依赖):一个Dependence是某个对象被建立的类型。

最简单的Angular 2中的依赖注入的方式以下:

import { Injector } from 'angular2/di';

var injector = Injector.resolveAndCreate([
  Car,
  Engine,
  Tires,
  Doors
]);
          
var car = injector.get(Car);

resolveAndCreate是一个静态的接口方法,根据输入的一系列的Binding来建立依赖的实例。然后,可使用injector.get()方法来获取某个Type/Token对应的对象实例。而在使用这个依赖时,可使用Angular 2内置的Inject:

import { Inject } from 'angular2/di';

class Car {
  constructor(
    @Inject(Engine) engine,
    @Inject(Tires) tires,
    @Inject(Doors) doors
  ) {
    ...
  }
}

Inject装饰器会自动将元数据绑定到Car类的属性中,也能够改写为TypeScript的方式:

class Car {
  constructor(engine: Engine, tires: Tires, doors: Doors) {
    ...
  }
}

到这一步,某个类能够声明它本身的依赖,而且被DI解析全部该类的依赖项。可是Injector还须要从Binding中获取如何去建立这些对象实例的信息。上文中是直接在resolveAndCreate方法中传入了一系列的Type/Token,而若是使用完整的写法,应该使用toClass方法显性的将某个Type/Token映射到某个实例。

import { bind } from 'angular2/di';

var injector = Injector.resolveAndCreate([
  bind(Car).toClass(Car),
  bind(Engine).toClass(Engine),
  bind(Tires).toClass(Tires),
  bind(Doors).toClass(Doors)
]);

上述方法中的token能够是任意的类型或者一个字符串,这也就是所谓的Recipe机制的具体实现。在这样的一种Binding的帮助下,不只仅Injector知道如何在应用过程当中使用这些依赖,而且配置了如何建立这些依赖。

进一步考虑,若是在应用中已经肯定了Foo类型,那又何须要写bind(Foo).toClass(Foo)这样的表达式,直接在程序中引入写死便可,而依赖注入的真正魅力在于:

bind(Engine).toClass(OtherEngine)

这样能够动态的为某个token绑定到依赖中,而且有效解决了命名空间冲突的问题。咱们能够建立一个相似与接口的类型,而后将它指向到具体的类型中。就像Java中的Interface与Implementation。

Other binding instructions

有时候,咱们并不必定须要将某个token绑定到某个类中,而是绑定到某个字符串值或者工厂方法中。

  • 绑定到值

bind(String).toValue('Hello World')
  • 绑定到别名

bind(Engine).toClass(Engine)
bind(V8).toAlias(Engine)

toAlias方法将某个token绑定到另外一个token中。

  • 绑定到某个工厂方法

bind(Engine).toFactory(() => {
  if (IS_V8) {
    return new V8Engine();
  } else {
    return new V6Engine();
  }
})

固然,某个工厂方法可能也有其依赖项,只要简单地将依赖项指向到参数中而且添加到token列表中便可。

bind(Engine).toFactory((dep1, dep2) => {
  if (IS_V8) {
    return new V8Engine();
  } else {
    return new V6Engine();
  }
}, [Token1, Token2])

Transient Dependencies and Child Injectors(短暂性传递与子注入器)

在上文中说起的依赖项每每都是单例化的,可是有时候咱们须要的是一个短暂的,即非单例模式的依赖,整体来讲有两种方式:

  • 使用工厂模式

bind(Engine).toFactory(() => {
  return new Engine();
})
  • 子注入器

可使用Injector.resolveAndCreateChild()这个方法迭代地建立子注入器,一个子注入器会继承父类注入器中声明的依赖项,可是若是子注入器中也是声明了某个依赖,那么它建立的实例与父注入器建立的一样的token的实例是不一致的:

var injector = Injector.resolveAndCreate([Engine]);
var childInjector = injector.resolveAndCreateChild([Engine]);

injector.get(Engine) !== childInjector.get(Engine);

Child injectors

Component Dependence

@Component({
  selector: 'app'
})
@View({
  template: '<h1>Hello !</h1>'
})
class App {
  constructor() {
    this.name = 'World';
  }
}
          
bootstrap(App);

上述声明的Component是直接将name写死在了代码里,若是将获取名字的这部分提取出来做为一个单独的服务:

class NameService {
  constructor() {
    this.name = 'Pascal';
  }

  getName() {
    return this.name;
  }
}

若是须要使用NameService,那么在声明某个Component时候,就须要使用@Inject装饰器:

class App {
  constructor(@Inject(NameService) NameService) {
    this.name = NameService.getName();
  }
}

若是是TypeScript,须要这么写:

class App {
  constructor(NameService: NameService) {
    this.name = NameService.getName();
  }
}

而NameService的Injector以及Binding这一步,其实就是由bootstrap方法实现的:

bootstrap(App, [NameService]);

固然,也能够写的更加优雅:

@Component({
  selector: 'app',
  bindings: [NameService]
})
@View({
  template: '<h1>Hello !</h1>'
})
class App {
  ...
}
相关文章
相关标签/搜索