Aurelia DI带有打字稿接口

时间:2015-06-16 16:05:03

标签: javascript dependency-injection aurelia

我已经浏览了Aurelia DI的文档并查看了源代码并希望分享我想要实现的内容,这样如果我遗漏了一些明显的东西,我就会被击落。我查看了Aurelia的样本here,但我看不出它是如何工作的,而且缺少文档。

我想要的是:

dataProvider.js (数据提供者界面)

export interface DataProvider {
  getData(): number;
}

itemDisplayer1.js (一个将使用注入的类来实现接口的类

import {inject} from 'aurelia-framework';
import {DataProvider} from './dataProvider';

@inject(DataProvider)
export class itemDisplayer1 {
  constructor(public dataProvider: DataProvider) {
    this.dataProvider = dataProvider;
    this.data = dataProvider.getData();
  }
}

itemDisplayer2.js (另一个将使用实现接口的注入类的类)

import {inject} from 'aurelia-framework';
import {DataProvider} from './dataProvider';

@inject(DataProvider)
export class itemDisplayer2 {
  constructor(public dataProvider: DataProvider) {
    this.dataProvider = dataProvider;
    this.data = dataProvider.getData();
  }
}

GoodDataProvider.js

import {DataProvider} from "./dataProvider";

export class GoodDataProvider implements DataProvider {
  data = 1;
  getData() {
    return this.data;
  }
}

BetterDataProvider.js

import {DataProvider} from "./dataProvider";

export class BetterDataProvider implements DataProvider {
  data = 2;
  getData() {
    return this.data;
  }
}

然后在某处(?)我想配置itemDisplayer1应该提供GoodDataProvider的实例,而itemDisplayer2应该提供BetterDataProvider(1)的实例。

然后是DI上下文的问题。我不知道如何使用container.createChild()。我找不到太多关于它的信息。它创建一个子容器,它将在需要时委托给父容器,但如果我创建了2个子容器并为每个子容器注册了2个提供者之一,那么itemDisplayer类将如何知道要使用哪个(不更改其定义)并注入父容器等)?

注意:生命周期管理信息不存在于消费者或依赖关系的提供者中(这通常在Aurelia DI示例中完成,似乎有点制造)。我希望能够在消费者和提供者关联时定义这一点 - 点上面的“(1)”。

总之,这可能吗?这是近期未来的事情吗?我是否应该尝试用满足我需求的自定义容器替换Aurelia DI?

(我之所以这样做是因为为了评估js框架,框架需要演示一个成熟的DI系统,并将生命周期管理/ AOP等功能作为标准之一)

5 个答案:

答案 0 :(得分:8)

来自@eisenbergeffect的

:一旦我们写完基准,DI就会进行一些内部改革。

但是在相关的说明中,它无法使用接口,因为TypeScript会在运行时编译它们。

当您在DI容器中注册不同类型,然后在@Inject(xxx)语句中指定相应的唯一键时,您必须提供唯一键。钥匙可以是你喜欢的任何东西。通常人们会将类型本身用于唯一键(这会导致一些混淆),但您可以使用字符串,数字或其他任何您喜欢的键。

单元测试也提供了信息:https://github.com/aurelia/dependency-injection/blob/master/test/container.spec.js

答案 1 :(得分:3)

正如Mike所说,Aurelia还不支持这种依赖性解析功能。并且接口被编译掉,因此它们不能用作密钥(例如container.registerInstance(ISomething, new ConcreteSomething());

然而,有一个小技巧可以使你看起来像是将接口本身用作关键。

foo.ts:

export interface IFoo {
  // interface
}

export const IFoo = Symbol();

bar.ts:

import {IFoo} from "./foo.ts";

export class Bar implements IFoo {
  // implementation
}

main.ts:

import {IFoo} from "./foo.ts";
import {Bar} from "./bar.ts";

...

container.registerInstance(IFoo, new Bar());

...

编译很好,编译器根据使用它的上下文知道何时使用正确的重复类型。

答案 2 :(得分:3)

因此,正如其他人所说,TS会编译接口,目前无法通过纯接口进行此操作。然而,TS的一个有趣且经常被遗漏的特征是它允许使用class作为接口,这使得能够解决当前的限制。

export abstract class DataProvider {
  getData(): number;
}

@singleton(DataProvider) // register with an alternative key
export class MyAwesomeDataProvider implements DataProvider {
}

@autoinject
export class DataConsumer {
  constructor(dataProvider: DataProvider) {
  }
}

在上面的代码中,我们声明了一个抽象类DataProvider,它将确保它不会被TS编译掉。然后,我们会向MyAwesomeDataProvider注册key替代DataProvider,每次请求MyAwesomeDataProvider时,都会返回DataProvider个实例。

就子容器而言,您需要container.createChild()返回容器的新实例,只要从该子容器触发解析,您就应该获得正确的实例。唯一的问题是使用具有两个冲突键的装饰器。基本上,元数据存在于类本身,因此您不能在DataProvider下注册两个实例,这肯定会(我自己没有尝试过)导致问题,唯一的方法就是是使用显式注册。 E.g。

export abstract class DataProvider {
  getData(): number;
}

export class MyAwesomeDataProvider implements DataProvider {
}

export class MyMoreAwesomeDataProvider implements DataProvider {
}        

child1 = container.createChild();
child1.registerSingleton(DataProvider, MyAwesomeDataProvider);

child2 = container.createChild();
child2.registerSingleton(DataProvider, MyMoreAwesomeDataProvider);

@autoinject
export class DataConsumer {
  constructor(dataProvider: DataProvider) {
  }
}

child1.get(DataConsumer); // injects MyAwesomeDataProvider
child2.get(DataConsumer); // injects MyMoreAwesomeDataProvider

答案 3 :(得分:0)

喜欢 Frank Gambino 的想法,并找到了一种方法,使其与@inject@autoinject一起使用。诀窍是使用自定义parameter decorator(因为接口在TypeScript中保留,我称之为@i)。

装饰者部分:

myClass.ts

import { autoinject } from 'aurelia-framework';    
import { i } from './i.ts';
import { IFoo } from "./ifoo.ts";    

@autoinject
export class MyClass {
    constructor(@i(IFoo) foo: IFoo) {
        foo.doSomething();
    }
}

i.ts:

import "reflect-metadata";

/**
 * Declare the interface type of a parameter.
 *
 * To understand more about how or why it works read here:
 * https://www.typescriptlang.org/docs/handbook/decorators.html#metadata
 */
export function i(interfaceSymbol: symbol) {
    return function (target: Object, parameterName: string | symbol, parameterIndex: number) {           
        var paramTypes = Reflect.getMetadata('design:paramtypes', target);
        paramTypes[parameterIndex] = interfaceSymbol;
        Reflect.defineMetadata('design:paramtypes', paramTypes, target);
    }
}

其余部分与 Frank Gambino 答案完全相同,但为了完整性我添加了它 ...

ifoo.ts:

export interface IFoo {
    doSomething(): void;
}

export const IFoo = Symbol("IFoo"); // naming the symbol isn't mandatory, but it's easier to debug if something goes wrong

some.ts:

import { IFoo } from "./ifoo.ts";

export class Bar implements IFoo {
    doSomething(): void {
        console.log('it works!');
    }
}

main.ts:

import { IFoo } from "./ifoo.ts";
import { Bar } from "./bar.ts";

...

container.registerInstance(IFoo, new Bar());

...

它实际上可以与其他DI容器一起使用。要使它与Angular2一起使用(虽然你为什么会这样?Aurelia更加棒极了:)你只需要将i.ts文件中interfaceSymbol的类型改为any而不是{{1}写Symobl("IFoo")(InjectionToken类是一个Angular的东西,很遗憾他们不支持Symbol作为注入令牌,至少目前是这样)。

答案 4 :(得分:0)

我采用另一种方法解决了对我有用的问题。

参加以下课程:

export class Foo implements Bar {

}

我将其更改为以下内容:

import { Container } from 'aurelia-framework';

class Foo implements Bar {
}

export var foo = Container.instance.get(Foo) as Bar;

现在我可以执行以下操作以获取该类的类型化单例实例:

import { foo } from 'foo';