Infuse.js is an IOC javascript library (inversion of control).
Using rules based on properties and paramaters naming, you will be able to inject data in functions or objects.
Quick video for the impatient:
Why would I need to inject anything anywhere?
Dependency injection is used in many languages, the concept is that some data are injected automatically when you create a “class” or other re-usable component.
Just a few advantages of using injection not to name them all. Injection will reduce boilerplate code (increasing readability), dependencies between components and you will be able to produce a much more re-usable code. Injection is also very good to produce a highly-testable code.
The application you are building will also benefit from lazy instantiation, which is very good, the instances will be created only when they are used and when you need them.
Infuse.js is a pretty small library compared to the huge benefit you can gain.
So… How does this work?
In short, you are going to create some rules based on names (string), you will provide data to inject (string, number, array, boolean, objects, function, and probably anything that can be hold in a variable).
When these rules are created, you are going to use their names as properties or constructor parameters. And the injector will take care of populating these variables with the right content, no strings attached!
Sorry I still don’t get it…
Let’s take an example then, you are building a huge app with several developers, javascript files, components, and so on. You have a basic function called “Settings” that will hold a lot of information and you like to use it in about anything in the app. How do you do that?
Well, you could create some kind of global object, you can access it everywhere and it is done!
window.Settings = { deploy: 'dev', domain: 'localhost' }
But in that case you will create a direct dependency on a object (Settings), which is bad for tons of reasons. Hard to test, not re-usable because of the dependencies, and so on. You should always stick to the Law of Demeter.
Another solution would be to create that object and pass it in everything you need, that would be a better a solution, but still very hard to maintain and not ideal.
With injection you can do the same thing in a much easier way (which is populating variable with something external), especially if you want to inject tons of things. But this could also be automatically done!
It would be nice to just say: “ok I’ve created these 6 components, I really would like to have that “settings” variable automatically set without doing anything or adding code for it!”. Well that’s what infuse.js will do.
Sounds good! Let’s see some code!
Well first, create an injector instance.
// create injector var injector = new infuse.Injector();
Now you create an object you want to access to from everywhere, just as an example, it could be a function, an array, a string, or whatever.
// create object var settings = { deploy: 'dev', domain: 'localhost' }
Now you want to say: “ok, every time something contains the variable ‘settings’, or has a constructor parameter ‘settings’, I want that object to be set in.”
That is nothing more than a mapping rule. Here is an example.
injector.mapValue('settings', settings);
Last step is populating the variables. This can be done in two ways: manually or automatically. Here is how to do that manually:
// a function var Receiver = function() { this.settings = null; } // create an instance var receiver = new Receiver(); // populate the settings variable injector.inject(receiver);
That’s basically it, you can add a postConstruct method, which will be automatically called when the injection is done.
Receiver.prototype.postConstruct = function() { alert(this.settings); // got it! }
Easy? Let’s see the automatic version where the injector will take care of instantiating the function and populating the variables.
// a function var Receiver = function() { this.settings = null; } Receiver.prototype.postConstruct = function() { alert(this.settings); // got it! } // create an instance var receiver = injector.createInstance(Receiver);
Works also with a constructor.
// a function var Receiver = function(settings) { this.mySettings = settings; alert(this.mySettings); // got it! } // create an instance var receiver = injector.createInstance(Receiver);
The injector can inject data in several ways, and can also take care of instantiation, of what should be injected and how it should injected. If we turn the data to a function:
var Settings = function() { this.deploy = 'dev'; this.domain = 'localhost'; }
We can use another type of rule (mapClass):
injector.mapClass('settings', Settings);
The injector will now inject a new instance of Settings every time it has to be injected somewhere (like doing a new Settings() and sending it).
This might not be ideal, maybe you want to inject the same instance all the time so you have some kind of shared unique instance? This is called a “Singleton mapping”. Don’t be scared by the name if you don’t like Singleton, they are not really Singleton. It just means that they are created only once and the same instance will be injected everywhere. You can do that by changing the rules like this:
injector.mapValue('settings', settings, true);
As the “settings” rule has been set “as Singleton”, the same settings instance will be injected in both functions.
var FooClass1 = function() { this.settings = null; } var FooClass2 = function() { this.settings = null; } var foo1 = injector.createInstance(FooClass1); var foo2 = injector.createInstance(FooClass2); // next line will alert true because the settings injected // will be the same instance (mapped as Singleton) alert(foo1.settings === foo2.settings);
This is just a small overview, you can do tons of things with injection and infuse.js.
Where do I get more info?
The repo is there, where you can find more examples (you can also check the tests in the repo to get a complete overview):
https://github.com/soundstep/infuse.js
Install with npm (infuse.js works with node.js):
npm install infuse.js
var Injector = require("infuse.js").Injector; var injector = new Injector(); injector.mapValue('name', 'John'); var Person = function(name) { this.nameParam = name; }; var john = injector.createInstance(Person); console.log(john.nameParam);
Comments
Nice work!
Still, how much more useful is this than :
// a function
var Receiver = function(settings) {
this.mySettings = settings;
alert(this.mySettings); // got it!
}
// create an instance
var receiver = new Receiver(window.getVar(“settings”));
where window.getVar() and window.setVar() are some functions to store variables (or in other words: “to couple a variable to a name”, like injector.mapValue() couples variables to a name).
Hey there.
Quick one just for the sake of it, It is not a good practice to pollute global objects (window), you could use a normal object for that. But it doesn’t matter, just a bad example I guess, I got your point.
– abstraction as you’ve done is cool (sending vars) and it helps, it is anyway better than using window inside Receiver. But it will require lot of boilerplate code. Imagine you have to inject 20 different things, in 30 different functions. It would also be bad to wrap them in a bigger objects, especially if they have no relations and if you don’t use everything. If a property needs a string, just send a string, not an object that contains a model, that contains a vo, that contains another object and that contains the string. See what I mean, direct access is always better. With dot notation you should not have more than one access level:
obj.myStuff // good
obj.obj.obj.myStuff // bad, can break at any point
Imaging you have something like:
var Receiver = function(settings) {
this.mySettings = settings;
this.myText = null;
this.myStyle = null;
this.myData = null;
this.myEmail = null;
this.myOtherStuff = null;
}
It becomes:
var Receiver = function(settings, text, style, data, postcode, email) {}
new Receiver(window.getVar(“settings”), window.getVar(“text”), window.getVar(“data”), window.getVar(“email”), window.getVar(“otherStuff”));
Adding or removing a parameter would require work both in the Receiver and when you instantiate it.
Imagine you have to do that in 20 receivers?
Yes you could have an object wrapper (a VO or something), but you still needs to make it, it might not make sense at that point for non-related object. What if you don’t want the same vars in the different Receivers?
That is what the injection is about: removing boilerplate code by automating. With injection it would stay:
injector.createInstance(Receiver);
A very important thing, when you create the receiver, you don’t need to know what it needs to work. You don’t go through that process of looking in the code “ok what does it needs to work correctly”, the Receiver is responsible for itself and ask what it needs.
And you’re free to easily add and remove vars from inside the Receiver, the instantiation might be done in another module with another developer working on it, etc.
– you still need to actually to call getVar() somewhere, while it is not needed with injection.
– there’s a big difference between giving and asking.
In your example, you are giving a variable to Receiver, with injection it is Receiver that is asking for one. So this can be changed and modify from inside the Receiver, it is more encapsulated.
It is a bit like “factory versus manual stuff”.
With the injector you do:
– give me instance, done, with everything ready inside.
Without:
– create the instance (new)
– fill it with stuff (window.getVar)
– fill it with another stuff (window.getVar)
– etc
Hope they are valid points and that it helps!
Romu
So in short:
1. Separation of concerns: functions should ask for the variables, but *not* get them themselves. The ‘outer world’ should insert those variables and be flexible in what variables they want to insert, but
2. we don’t want to repeat ourselves, so there should be an easy way to insert the variables time and time again when they remain semi-static. This is done using a prepared injector.
It’s a combination of SOC and DRY then, right?
Could be seen this way.
For DRY it will help but at a very small level, I would say more: “it is going to remove boilerplate code”. Saying that it is good for DRY is a bit wrong. DRY is an overall concept that you have to apply when you code. An injector will not solve all your problems.
SOC definitely yes. I’m a maniac when it comes to decoupling (see my framework soma.js, built only for that). The injector has been built to remove the boilerplate code in the framework, keep the decoupling, and access to components easily. I’ll integrate it in a further version.
There’s always 2 things I keep in mind when I code a view or a model (in any language):
– Can I take it out of that app to put it somewhere else without doing anything?
– Can I take it out of that framework and use it in another without doing anything?
This is very hard job, not always possible, but I always try to do it.
Injection has brought answer to these questions in other languages. I think it is going to work perfectly well in js. See AngularJS for example, a model is nothing more that a pure javascript function where they inject the scope and what’s needed. So you can use it in something else without pain. That’s the way to go. Yes it will still need the data from the outside, but that’s completely fine as long as it does the job it should and notify the outside (another hard problem to solve).
The injector might be an help to build your own “mini-framework” if you like to handle things yourself and if you don’t want to use a bigger framework.
For people that don’t really get injection, all the frameworks like AngularJS will feel a bit like magic. With a very small injector like this, you can shape a bit of that magic yourself, for what you need. I think you can get huge advantages from it.
Also don’t forget unit testing, injection is going to help a lot in that too.
Thanks for all your elaborate answers 🙂
I’ll definitely try this out!
No worries, that’s subjects that I like. Happy to help!
Send me some feedbacks please if you do so, there’s always room for improvement.
Pingback: soma-events native DOM 3 Observer pattern | Soundstep
Nice framework, I have also developed a IoC container for JavaScript apps. Please check it out at http://blog.wolksoftware.com/introducing-inversifyjs