Inversifyjs: JavaScript decorators

Created on 15 Sep 2016  路  17Comments  路  Source: inversify/InversifyJS

Is it at all possible to use Inversify decorators in JavaScript rather than TypeScript? Or is the decorate() function the only way?

Babel has the syntax-decorators and transform-decorators-legacy plugins but I can't seem to get them working with @inject().

What I've done is basically copied the basic JS example and replaced the decorate() calls with decorators. Here is the result.

Run through Babel, that code fails with Missing required @inject or @multiInject annotation in: argument 0 in class Ninja. Looking at the compiled code, there's no mention of inject so I take it ES has no notion of decorators on parameters.

Is there a recommended approach here? There's already #29 but it seems to apply to the previous syntax and doesn't really offer a solution.

Sorry for not using the issue template but it really didn't apply.

babeljs docs

All 17 comments

Hi @timdp,

Thanks for the feedback. I think I have seem something online in the past about typescript decorators and babel decorators having incompatibility issues.

I'm not an user of babel so I have not encountered this problem but I would like to support both babel and typescript.

I need to do some research before I can start to work on this. If you would like to help that would be awesome. Please let me know if you know a link or repo in Github that solves this issue already.

Thanks! I haven't done much beyond some experiments with ES decorators but if I come across something useful, I'll definitely post it here.

I just found "Enabling decorators in your transpiler" maybe it can help you to solve your issue.

Thanks! If you find the solution we will document it in the wiki :smile:

Using ES7 Decorators with Babel 6 is probably what you are looking for.

I'd already googled my way to both those links, but thanks. :smile:

I still think the main reason why it doesn't work is because Babel doesn't know what to do with the decorators on the parameters.

I've updated the Gist to use instance properties instead:

@injectable()
class Ninja {
  @inject(TYPES.Katana) _katana = null

  @inject(TYPES.Shuriken) _shuriken = null

  fight () { return this._katana.hit() }
  sneak () { return this._shuriken.throw() }
}

This requires babel-plugin-transform-class-properties to be loaded first. However, the properties still just disappear because of the @inject (without it, they get moved to the constructor).

I wonder if it's even possible to use the current implementation of inject with JS? I mean, if you use it with decorate, it expects the index of the parameter ...

Well, the problem is that in JS we don't have class properties. This means that at run-time property injection doesn't really make sense. For this reason, only constructor injection is supported when using the decorate function:

var inversify = require("inversify");
require("reflect-metadata");

var TYPES = {
  Ninja: 'Ninja',
  Katana: 'Katana',
  Shuriken: 'Shuriken'
}

class Katana {
  hit () {
    return 'cut!'
  }
}

inversify.decorate(inversify.injectable(), Katana);

class Shuriken {
  throw () {
    return 'hit!'
  }
}

inversify.decorate(inversify.injectable(), Shuriken);

class Ninja {

  constructor(katana, shuriken) {
      this._katana = katana;
      this._shuriken = shuriken;
  }

  fight () { return this._katana.hit() }
  sneak () { return this._shuriken.throw() }

}

inversify.decorate(inversify.injectable(), Ninja);
inversify.decorate(inversify.inject(TYPES.Katana), Ninja, 0);
inversify.decorate(inversify.inject(TYPES.Shuriken), Ninja, 1);

// Declare bindings
var kernel = new inversify.Kernel()
kernel.bind(TYPES.Ninja).to(Ninja);
kernel.bind(TYPES.Katana).to(Katana);
kernel.bind(TYPES.Shuriken).to(Shuriken);

// Resolve dependencies
var ninja = kernel.get(TYPES.Ninja);
console.log(ninja.fight(), ninja.sneak());

So there's no way to avoid decorate altogether? Perhaps with an alternative decorator such as the following construct:

@injectable()
@inject({
  _katana: TYPES.Katana,
  _shuriken: TYPES.Shuriken
})
class Ninja {
  fight () { return this._katana.hit() }
  sneak () { return this._shuriken.throw() }
}

Not saying it's perfect but it would solve a common case and not require decorate.

That is kind of out of the way the other decorators work and could lead to confusion of our users. I don't think we should go that way.

I understand that the JS API is too verbose but you could create some helpers:

Declare a annotate helper function

This helper reduces the annotation code.

function annotate(constructor, dependencies) {
    inversify.decorate(inversify.injectable(), constructor);
    (dependencies || []).forEach(function(dependency, index) {
        inversify.decorate(inversify.inject(dependency), constructor, index);
    });
}
var inversify = require("inversify");
require("reflect-metadata");

var TYPES = {
  Ninja: 'Ninja',
  Katana: 'Katana',
  Shuriken: 'Shuriken'
}

class Katana {
  hit () {
    return 'cut!'
  }
}

annotate(Katana);

class Shuriken {
  throw () {
    return 'hit!'
  }
}

annotate(Shuriken);

class Ninja {

  constructor(katana, shuriken) {
      this._katana = katana;
      this._shuriken = shuriken;
  }

  fight () { return this._katana.hit() }
  sneak () { return this._shuriken.throw() }

}

annotate(Ninja, [TYPES.Katana, TYPES.Shuriken]);

// Declare bindings
var kernel = new inversify.Kernel()
kernel.bind(TYPES.Ninja).to(Ninja);
kernel.bind(TYPES.Katana).to(Katana);
kernel.bind(TYPES.Shuriken).to(Shuriken);

// Resolve dependencies
var ninja = kernel.get(TYPES.Ninja);
console.log(ninja.fight(), ninja.sneak());

Declare register helper function

This helper reduces annotation and binding registration code.

function register(kernel, identifier, constructor, dependencies) {
    inversify.decorate(inversify.injectable(), constructor);
    (dependencies || []).forEach(function(dependency, index) {
        inversify.decorate(inversify.inject(dependency), constructor, index);
    });
    return kernel.bind(identifier).to(constructor);
}
var inversify = require("inversify");
require("reflect-metadata");

var TYPES = {
  Ninja: 'Ninja',
  Katana: 'Katana',
  Shuriken: 'Shuriken'
}

class Katana {
  hit () {
    return 'cut!'
  }
}

class Shuriken {
  throw () {
    return 'hit!'
  }
}

class Ninja {

  constructor(katana, shuriken) {
      this._katana = katana;
      this._shuriken = shuriken;
  }

  fight () { return this._katana.hit() }
  sneak () { return this._shuriken.throw() }

}

// Declare bindings
var kernel = new inversify.Kernel()
register(kernel, TYPES.Katana, Katana);
register(kernel, TYPES.Shuriken, Shuriken);
register(kernel, TYPES.Ninja, Ninja, [TYPES.Katana, TYPES.Shuriken]);

// Resolve dependencies
var ninja = kernel.get(TYPES.Ninja);
console.log(ninja.fight(), ninja.sneak());

True, I was already thinking about a lightweight adapter around the API, but it'd be nice if it were supported out of the box. Or someone could create an inversify-helpers package. If not, I'd at least consider adding the comment above to the docs. :-)

I will add something to the wiki. Adding inversify-vanillajs-helpers sounds like a good idea :smile:

Perfect! do you want me to create a repo for inversify-vanillajs-helpers under the inversify organization?

Not my call but I think a lot of people would find it useful!

What I mean is that I can create the repo as an officially supported repo and you can help us. Or you can create the repo under your user if you prefer that.

Oh, I see. I appreciate the offer but I'm not really interested in maintaining another project at this point. Thanks anyway. :-)

No problem, we will create it them :smile:

I'm closing this issue you can follow https://github.com/inversify/InversifyJS/issues/375 to track the implementation of the JS helpers

Was this page helpful?
0 / 5 - 0 ratings