Lazy property injection has vanillajs support
It is not possible to use lazy property injection with vanillajs.
We need to upgrade the decorate function so it also supports vanillajs.
Inversify.decorate(lazyInject(TYPES.SomeDependency), MyComponent, "_someProperty");
So the following is possible:
var Inversify = require("[email protected]");
var InversifyInjectDecorators = require("[email protected]");
var InversifyVanillaJS = require("[email protected]");
var myContainer = new Inversify.Container();
var { lazyInject } = InversifyInjectDecorators.getDecorators(container);
var TYPES = {
SomeDependency: "SomeDependency"
}
myContainer.bind(TYPES.SomeDependency).toConstantValue(
{ doSomething: () => "something" }
);
class MyComponent {
constructor() {
this._someProperty = null;
}
render() {
return (
`<div>${his._someProperty.doSomething()}</div>`;
);
}
}
InversifyVanillaJS.helpers.annotate(
MyComponent,
[
/* Nothing to inject*/
]
);
Inversify.decorate(lazyInject(TYPES.SomeDependency), MyComponent, "_someProperty");
https://stackoverflow.com/questions/42040510/ioc-in-react-without-typescript
How is the progress coming along with this? Can I help in any way? (test, etc.)
@sebastian-zarzycki-es sorry for the late reply. This is going well, I expect to release it during the weekend. Once I release it I will need your help to confirm that it solves your problem.
Fantastic! I will definitely check it out as soon as it's available.
After doing some investigation it turns out that no changes were required to the lib. I was doing it wrong:
Inversify.decorate(lazyInject(TYPES.SomeDependency), MyComponent, "_someProperty");
Should have been:
lazyInject(TYPES.SomeDependency)(MyComponent.prototype, "_someProperty");
Here is a full example:
var Inversify = require("inversify");
var getDecorators = require("inversify-inject-decorators").default;
var helpers = require("inversify-vanillajs-helpers").helpers;
require("reflect-metadata");
var TYPES = {
SomeComponent: "SomeComponent",
SomeDependency: "SomeDependency"
};
class MyComponent {
constructor() {
this._someProperty; // declare it this way or it won't work :(
}
render() {
return this._someProperty.doSomething();
}
}
var myContainer = new Inversify.Container();
var lazyInject = getDecorators(myContainer).lazyInject;
helpers.annotate(MyComponent, []); // required because inversify invokes "new"
lazyInject(TYPES.SomeDependency)(MyComponent.prototype, "_someProperty");
myContainer.bind(TYPES.SomeDependency).toConstantValue({
doSomething: function () {
return "something";
}
});
myContainer.bind(TYPES.SomeComponent).to(MyComponent);
var component = myContainer.get(TYPES.SomeComponent); // inversify invokes "new"
console.log(component.render());
In the case of React the component instance is not created by Inversify and the test case is a bit different:
var Inversify = require("inversify");
var getDecorators = require("inversify-inject-decorators").default;
var helpers = require("inversify-vanillajs-helpers").helpers;
require("reflect-metadata");
var TYPES = {
SomeComponent: "SomeComponent",
SomeDependency: "SomeDependency"
};
class MyComponent {
constructor(props) {
this.props = props;
this._someProperty; // declare it this way or it won't work :(
}
render() {
return this._someProperty.doSomething();
}
}
var myContainer = new Inversify.Container();
var lazyInject = getDecorators(myContainer).lazyInject;
// helpers.annotate(MyComponent, []); Not required because we invoke "new"
lazyInject(TYPES.SomeDependency)(MyComponent.prototype, "_someProperty");
myContainer.bind(TYPES.SomeDependency).toConstantValue({
doSomething: function () {
return "something";
}
});
myContainer.bind(TYPES.SomeComponent).to(MyComponent);
var component = new MyComponent({ prop1: "prop1" }); // we invoke "new"
console.log(component.render());
console.log(component.props.prop1);
I have added both test cases to the inversify-vanillajs-helpers unit tests.
I would recommend creating a small helper:
function lazyInjectHelper(type: any, clss: any, prop: string) {
lazyInject(type)(clss.prototype, prop);
}
lazyInjectHelper(TYPES.SomeDependency, MyComponent, "_someProperty");
I'm not sure I follow.
this._someProperty; // declare it this way or it won't work :(
What does it mean exactly? Declare it what way? In constructor or with underscore?
Also, I'm not sure how related to my original issue - https://github.com/inversify/InversifyJS/issues/514 - where I was asking whether it's possible to use decorators in vanillajs, to achieve similar effect. I believe it's doable, but either I cannot find proper syntax, or something's not supported yet. Could you please refer to that?
You probably want to add something to your source code to let other developers that the class has a property. You would probably use:
this._someProperty = null;
But that causes the lazy injection to fail so I recommend using the following instead:
this._someProperty;
About your original question:
There's no example using ES6 decorators or injecting property by name, not by index.
It can be achieved using the following:
lazyInject(TYPES.SomeDependency)(MyComponent.prototype, "_someProperty");
You could create babel decorator that invokes that under the hood. Or if you are not using decorators ar all just use a function:
function lazyInjectHelper(type: any, clss: any, prop: string) {
lazyInject(type)(clss.prototype, prop);
}
lazyInjectHelper(TYPES.SomeDependency, MyComponent, "_someProperty");
Why does this._someProperty = null; cause lazy injection to fail? Because the value is already defined? Would assignment to undefined help? Why do you keep using the underscore in the examples - is it significant?
Also, I was under the impression that lazyInject already is a decorator and it has , as specified in https://github.com/inversify/inversify-inject-decorators package. So I'm even more confused here. My idea was to use the syntax as presented in #514, using @lazyInject. I guess I'm having a hard time with understanding the difference. Are TS decorators not the same thing as Babel's? If not, how would I go about making it compatible? Couldn't I annotate properties directly, using the syntax in #514?
Underscore _ is used as a naming convention to indicate that a property is mean to be private.
Decorators are just functions:
function lazyInject(id) {
return function (proto, prop) {
// do something ...
}
}
You can invoke them using @lazyInject(TYPE.SomeDependency) and the TypeScript compiler will generate some code that calls it as:
lazyInject(TYPES.SomeDependency)(MyComponent.prototype, "_someProperty");
This means that if you are not using TypeScript you can just call it as a function. Just like the code generated by the TypeScript compiler.
There was a change in the decorator proposal and the TypeScript decorators are not compatible with the babel ones. At some point they will be the same but TypeScript needs to do an update. I don't know the details because I don't use babel but I'm sure there is info online... So if you want to use a decorator using the @ syntax you will need to write a babel compatible decorator (function).
Does that make sense?
Kind of, thanks. My main goal is to avoid specifying the (MyComponent.prototype, "_someProperty") part. If decorator decorates a property, then that property name and the class it belongs to, should already be passed into the decorator for use. It seems that I would have to figure out that part on my own.
Are you using babel? I can try to investigate this evening. Won't promise I will figure out but I can try to help.
I do, yes. Specifically - "babel-plugin-transform-decorators-legacy" plugin.
Ouch, more issues. When using lazyInject (even as function, for now), even though the actual inject works, it seems to break the default MobX @observable functionality - I think there are some getter conflicts. I'm beginning to doubt it could all coexist - mobx/react/inversify, that is :(
We define a custom getter and setter. @observable probably do the same and that is why they are incompatible :(
I know there are project using mobx with React and inversify but they use TypeScript and they don't mix lazyInject with @observable. For example dwatch uses lazyInject as you can see here.
Yeah. I guess I can work around this by injecting one property and then assigning it to another @observable property. A bit clunky, but doable.
Still, using lazyInject as decorator would be nice, nonetheless. Do you think there's a way to do this?
As you can see in the Dwatch source code they use it as a decorator and I know that some people have used inversify with babel. I'm sorry I'm not experienced with Babel maybe @lholznagel can help? If not I suggest you ask some of the members of the Babel community.
Nope, sorry.
Never used babel always typescript.
@lholznagel thanks for taking a look :+1: