inversify / InversifyJS

A powerful and lightweight inversion of control container for JavaScript & Node.js apps powered by TypeScript.
http://inversify.io/
MIT License
11.33k stars 718 forks source link

Proposal: Decorators #33

Closed jamesadarich closed 8 years ago

jamesadarich commented 8 years ago

Problem: Currently we rely on the constructor argument names to inject dependencies. This causes problems with minification (as the argument name is changed) and injecting two dependencies of the same type into a class (as they both can't have the same argument name).

Proposed Solution: Decorators

As an addition to being able to resolve the dependencies of the class by argument name, it should also possible to do so via decorator (see example below)


class A {
   constructor (@Inject("IB") be: IB) {
      be.fancy();
   }
}

interface IB {
    fancy(): void;
}

class B implements IB {
     fancy() { 
        console.log("this does something I promise");
     }
}

The decorator should be given the string which relates to the binding registered in the kernel i.e. in this scenario...


let kernel = new inversify.Kernel();
kernel.bind(new inversify.TypeBinding<IB>("IB", B));

In terms of solutions I have already developed a brief concept so I suggest that we add the following...


let Inject = function (typeIdentifier: string) {
     return function (target: InjectableConstructorInterface, propertyName: string, argumentIndex: number) {
         //if there are no decorated argument types associated with this type
         //then add them as the argument names (as currently implemented)
         if (!target.argumentTypes) {
            // Regular expressions used to get a list containing
            // the names of the arguments of a function
            let STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
            let ARGUMENT_NAMES = /([^\s,]+)/g;

            let fnStr = target.toString().replace(STRIP_COMMENTS, '');
            let argsInit = fnStr.indexOf('(') + 1;
            let argsEnd = fnStr.indexOf(')');

            target.argumentTypes = fnStr.slice(argsInit, argsEnd).match(ARGUMENT_NAMES);
         }
         //override assumed type with the decorated type
         target.argumentTypes[argumentIndex] = typeIdentifier;
     };
 };
 export { Inject };

then adjust how _getConstructorArguments works slightly to always return the argumentTypes array in favor of the new decorator defined arguments if these are available.


 interface InjectableConstructorInterface {
     argumentTypes: Array<string>;
 }

private _getConstructorArguments(func : InjectableConstructorInterface) {
     if (func.argumentTypes) {
        return func.argumentTypes;
     }
    ... (continues to execute the existing code)

What are your thoughts @remojansen? I can have this code ready in a PR if you're happy, I'm also happy to discuss naming or tweaks/additions in functionality.

This should resolve #29 and #14 :)

Jameskmonger commented 8 years ago

:+1:

remojansen commented 8 years ago

@jamesrichford thanks a lot again for helping me with this project :+1: I agree with everything you said. I think that if we can do just as you said and support still decorators in 1.x (as long as we still support the old way without decorators) that would be great.

I you could send that pull request it would be great.

Also I would like to share with you some ideas about InversifyJS 2.0 I want to explain you why is taking so long :disappointed: and what I have in mind. I have created a mailing list at https://groups.google.com/forum/#!topic/inversifyjs/06opbRx6_I8 please join and I will share with you my plans :smile:

jamesadarich commented 8 years ago

Nice one, I requested access. I'll wing you a PR over asap :)