thiagobustamante / typescript-ioc

A Lightweight annotation-based dependency injection container for typescript.
MIT License
526 stars 64 forks source link

override Container.bind() #10

Closed nilobarp closed 7 years ago

nilobarp commented 7 years ago

Is there a way to override the bound types? For example:

Container.bind(AuthGuard).to(MockGuard);
    ...
Container.bind(AuthGuard).to(AnotherMockGuard);

Currently I am not able to bind AnotherMockGuard; Container.get(AuthGuard) returns MockGuard

thiagobustamante commented 7 years ago

It was designed to support it. I will take a look now.

thiagobustamante commented 7 years ago

I am trying to reproduce it. I added a test case, but my test is not failing. It is working as expected.

Please, can you provide me your failing code to help to reproduce the problem?

nilobarp commented 7 years ago

Must have done something unusual. It works just as shown by your tests.

mamingzhi commented 7 years ago

Hi @thiagobustamante : (First it is not an issue, I could not find where I can put my comments, so I put it here.) Like Spring in Java, Both of typescript-ioc &typescript-rest are excellent projects in Typescript, I like them better compared with other DI framework and Ioc container for Typescript. :+1:

But for Interface, I found the section as below,

A note about classes and interfaces
Typescript interfaces only exists at development time, to ensure type checkings. When compiled, they generates nothing to runtime code. It ensures a good performance, but also means that is not possible to use interfaces as the type of a property being injected. There is no runtime information that could allow any reflection on interface type. Take a look at https://github.com/Microsoft/TypeScript/issues/3628 for more information about this.

I understood the concern of typescript-ioc team. However, when I talk about Interface injection in typescript with my friend, he said "the InversifyJS can do that". I did a quick look at the "InversifyJS", yes, it can inject a class as interface type. but the way are complicated & awkward.

Can typescript-ioc team borrow some ideas to implement this feature? It will be helpful for some existing typescript codes. (They want to put their codes in Ioc container and there are a lots of interface type.)

Please refer the https://github.com/inversify/InversifyJS. (By the way, typescript-ioc is more clear and easy to learn. great project! :+1: )

thiagobustamante commented 7 years ago

Hi @mamingzhi,

Thanks for your feedback! I tried for a long time to find a solution for this problem... I started to accept that it would not be solved as I expected (at least not before typescript team creates some support for it), when I found that post:

https://github.com/Microsoft/TypeScript/issues/3628

The problem is that there is no information about interfaces at runtime. The typescript compiler removes any interface code during the compilation. It uses interfaces only for type checks during compilation.

If you observe in that thread, you will find a lot of comments from remojansen there. He is the Inversify creator. He also did not found a way to solve the problem how he wanted.

The perfect solution would be if we could just write:

interface MyInterface {}

@Provides(MyInterface)
class MyClass implements MyInterface {}

Once we do not have how to find runtime information to implement this, we need to add some kind of information that could be retrieved by our injector.

Inverify workaround this problem adding a string identifier to the injector. So, this identifier is the information it uses at runtime to reference the desired class:

let TYPES = {
    Warrior: Symbol("Warrior"),
    Weapon: Symbol("Weapon"),
    ThrowableWeapon: Symbol("ThrowableWeapon")
};

@injectable()
class Ninja implements Warrior {
    private _katana: Weapon;
    private _shuriken: ThrowableWeapon;
    public constructor(
        @inject(TYPES.Weapon) katana: Weapon,
        @inject(TYPES.ThrowableWeapon) shuriken: ThrowableWeapon
    ) {
        this._katana = katana;
        this._shuriken = shuriken;
    }
}

All injections are resolved based on the type identifier, and not on the type itself. It can make the interfaces problem disappear, but turns the contract confuse to use.

Typescript-ioc does not use anything else to reference the type other then the type itself. It makes it simpler to use, but does not give me anything to use to support interfaces.

It is not perfect, but in typescript we can use abstract classes with the exact same semantics then interfaces in Java

abstract class MyInterface {}

@Provides(MyInterface)
class MyClass implements MyInterface {}

The only difference is that it is not removed during compilation and keeps the code at runtime (And the syntax, of course).

It is possible to add the same support that Inversify implemented. (Create a map somewhere for the interfaces, using strings or symbols and use them to bind the injections). We can open a new issue for this and try to find together a syntax that pleases everyone, or keep waiting some solution from typescript and reflect-metadata teams. The issue there is still open and being actively discussed. I really would appreciate any new ideas for this.