Dependency injection is a software design pattern that implements inversion of control (IoC) for resolving dependencies.
In simple words, dependency injection means giving an object its instance variables when needed.
Build a program that implements DI and launches a rocket into space!
To do so, we have to push a big red button with a lid.
function launchRocket() {
console.log('Launching rocket at ' + new Date);
}function BigRedButton() {
}
BigRedButton.prototype.openLid = function() {
this._open = true;
};
BigRedButton.prototype.closeLid = function() {
this._open = false;
};
BigRedButton.prototype.push = function() {
if (this._open) {
launchRocket();
}
};function spaceProgram() {
var button = new BigRedButton();
button.openLid();
button.push();
button.closeLid();
}The rocket launcher
function launchRocket() {
console.log('Launching rocket at ' + new Date);
}
function BigRedButton() {
}
BigRedButton.prototype.openLid = function() {
this._open = true;
};
BigRedButton.prototype.closeLid = function() {
this._open = false;
};
BigRedButton.prototype.push = function() {
if (this._open) {
launchRocket();
}
};
function spaceProgram() {
var button = new BigRedButton();
button.openLid();
button.push();
button.closeLid();
}
spaceProgram();function launchRocket() {
console.log('Launching rocket at ' + new Date);
}
function BigRedButton() {
}
BigRedButton.prototype.openLid = function() {
this._open = true;
};
BigRedButton.prototype.closeLid = function() {
this._open = false;
};
BigRedButton.prototype.push = function() {
if (this._open) {
launchRocket();
}
};
function spaceProgram() {
var button = new BigRedButton();
button.openLid();
button.push();
button.closeLid();
}function BigRedButton(buttonAction) {
this.action = buttonAction;
}
BigRedButton.prototype.openLid = function() {
this._open = true;
};
BigRedButton.prototype.closeLid = function() {
this._.open = false;
};
BigRedButton.prototype.push = function() {
if (this._open) {
this.action();
}
};function spaceProgram() {
var button = new BigRedButton(launchRocket);
button.openLid();
button.push();
button.closeLid();
}function spaceProgram(button) {
button.openLid();
button.push();
button.closeLid();
}function launchRocket() {
console.log('Launching rocket at ' + new Date);
}
function BigRedButton(buttonAction) {
this.action = buttonAction;
}
BigRedButton.prototype.openLid = function() {
this._open = true;
};
BigRedButton.prototype.closeLid = function() {
this._open = false;
};
BigRedButton.prototype.push = function() {
if (this._open) {
this.action();
}
};
function spaceProgram(button) {
button.openLid();
button.push();
button.closeLid();
}
spaceProgram(new BigRedButton(launchRocket));
But what if we had larger application?
it's difficult to maintain a large dependency graph manually
:(
Where we register application components and ask to inject them wherever they might be needed
"Hi injector! here's something called 'button'.
It is just a simple value that you can give to anyone who needs it"
var injector = createInjector();
injector.constant('button', new BigRedButton(launchRocket));var injector = createInjector();
injector.constant('button', new BigRedButton(launchRocket));
injector.invoke(spaceProgram);function spaceProgram(button) {
button.openLid();
button.push();
button.closeLid();
}"I need it!"
function createInjector() {
function constant(name, value) {
}
function invoke(fn) {
}
return {
constant: constant,
invoke: invoke
};
}function createInjector() {
var cache = {};
function constant(name, value) {
cache[name] = value;
}
function invoke(fn) {
}
return {
constant: constant,
invoke: invoke
};
}How can we figure out fn's expected dependencies ?
spaceProgram.toString();
> "function spaceProgram(button) {
> button.openLid();
> button.push();
> button.closeLid();
> }"
spaceProgram
.toString()
.match(/^function\s*[^\(]*\(([^\)]*)\)/)[1]
.split(','); // => ['button']^function
\s*
[^\(]*
\(
(
[^\)]*
)
\)
The source code always begins with the text 'function'
It is followed by (optionally) some white space
Then there is (optionally) the function's name, which we define as a succession of
characters other than the opening parenthesis.
Then comes the argument list, which starts with an opening parenthesis.
The argument list is captured into a capturing group, because it is the part we're
interested in.
What we capture is a succession of characters other than the closing parenthesis.
Then we close the capturing group.
Finally we match the closing parenthesis.
/^function\s*[^\(]*\(([^\)]*)\)/function createInjector() {
var cache = {};
function constant(name, value) {
cache[name] = value;
}
function invoke(fn) {
var argsString = fn.toString()
.match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
var argNames = argsString.split(',').map(function(argName) {
return argName.replace(/\s*/g, '');
});
}
return {
constant: constant,
invoke: invoke
};
}function createInjector() {
var cache = {};
function constant(name, value) {
cache[name] = value;
}
function invoke(fn) {
var argsString = fn.toString()
.match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
var argNames = argsString.split(',').map(function(argName) {
return argName.replace(/\s*/g, '');
});
var args = argNames.map(function(argName) {
return cache[argName];
});
return fn.apply(null, args);
}
return {
constant: constant,
invoke: invoke
};
}Back to our Injector
// Framework
function createInjector() {
var cache = {};
function constant(name, value) {
cache[name]= value;
}
function invoke(fn) {
var argsString = fn.toString()
.match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
var argNames = argsString.split(',').map(function(argName) {
return argName.replace(/\s*/g, '');
});
var args = argNames.map(function(argName) {
return cache[argName];
});
return fn.apply(null, args);
}
return {
constant: constant,
invoke: invoke
};
}
// App
function launchRocket() {
console.log('Launching rocket at '+new Date());
}
function BigRedButton(buttonAction) {
this.action = buttonAction;
}
BigRedButton.prototype.openLid = function() {
this._open = true;
};
BigRedButton.prototype.closeLid = function() {
this._open = false;
};
BigRedButton.prototype.push = function() {
if (this._open) {
this.action();
}
};
function spaceProgram(button) {
button.openLid();
button.push();
button.closeLid();
}
var injector = createInjector();
injector.constant('button', new BigRedButton(launchRocket));
injector.invoke(spaceProgram);
we still have to manually construct the dependency between the big red button and the launchRocket function
new BigRedButton(launchRocket)injector.factory('button', function(buttonAction) {
return new BigRedButton(buttonAction);
});
injector.constant('buttonAction', launchRocket);function createInjector() {
var cache = {};
function constant(name, value) {
cache[name] = value;
}
function factory(name, factoryFn) {
// todo
}
function invoke(fn) {
var argsString = fn.toString()
.match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
var argNames = argsString.split(',').map(function(argName) {
return argName.replace(/\s*/g, '');
});
var args = argNames.map(function(argName) {
return cache[argName];
});
return fn.apply(null, args);
}
return {
constant: constant,
factory: factory,
invoke: invoke
};
}function createInjector() {
var instanceCache = {}; //*
var providerCache = {}; //*
function constant(name, value) {
instanceCache[name] = value; //*
}
function factory(name, factoryFn) {
providerCache[name] = factoryFn; //*
}
function invoke(fn) {
var argsString = fn.toString()
.match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
var argNames = argsString.split(',').map(function(argName) {
return argName.replace(/\s*/g, '');
});
var args = argNames.map(function(argName) {
return instanceCache[name]; //*
});
return fn.apply(null, args);
}
return {
constant: constant,
factory: factory,
invoke: invoke
};
}function createInjector() {
var instanceCache = {};
var providerCache = {};
function constant(name, value) {
instanceCache[name] = value;
}
function factory(name, factoryFn) {
providerCache[name] = factoryFn;
}
function invoke(fn) {
var argsString = fn.toString()
.match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
var argNames = argsString.split(',').map(function(argName) {
return argName.replace(/\s*/g, '');
});
var args = argNames.map(function(argName) {
if (instanceCache.hasOwnProperty(argName)) {
return instanceCache[argName];
} else if (providerCache.hasOwnProperty(argName)) {
var provider = providerCache[argName];
}
});
return fn.apply(null, args);
}
return {
constant: constant,
factory: factory,
invoke: invoke
};
}function createInjector() {
var instanceCache = {};
var providerCache = {};
function constant(name, value) {
instanceCache[name] = value;
}
function factory(name, factoryFn) {
providerCache[name] = factoryFn;
}
function invoke(fn) {
var argsString = fn.toString()
.match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
var argNames = argsString.split(',').map(function(argName) {
return argName.replace(/\s*/g, '');
});
var args = argNames.map(function(argName) {
if (instanceCache.hasOwnProperty(argName)) {
return instanceCache[argName];
} else if (providerCache.hasOwnProperty(argName)) {
var provider = providerCache[argName];
var instance = invoke(provider);
return instance;
}
});
return fn.apply(null, args);
}
return {
constant: constant,
factory: factory,
invoke: invoke
};
}function createInjector() {
var instanceCache = {};
var providerCache = {};
function constant(name, value) {
instanceCache[name] = value;
}
function factory(name, factoryFn) {
providerCache[name] = factoryFn;
}
function invoke(fn) {
var argsString = fn.toString()
.match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
var argNames = argsString.split(',').map(function(argName) {
return argName.replace(/\s*/g, '');
});
var args = argNames.map(function(argName) {
if (instanceCache.hasOwnProperty(argName)) {
return instanceCache[argName];
} else if (providerCache.hasOwnProperty(argName)) {
var provider = providerCache[argName];
var instance = invoke(provider);
instanceCache[argName] = instance;
return instance;
}
});
return fn.apply(null, args);
}
return {
constant: constant,
factory: factory,
invoke: invoke
};
}// Framework
function createInjector() {
var instanceCache = {};
var providerCache = {};
function constant(name, value) {
instanceCache[name]= value;
}
function factory(name, factoryFn) {
providerCache[name] = factoryFn;
}
function invoke(fn) {
var argsString = fn.toString()
.match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
var argNames = argsString.split(',').map(function(argName) {
return argName.replace(/\s*/g, '');
});
var args = argNames.map(function(argName) {
if (instanceCache.hasOwnProperty(argName)) {
return instanceCache[argName];
} else if (providerCache.hasOwnProperty(argName)) {
var provider = providerCache[argName];
var instance = invoke(provider);
instanceCache[argName]= instance;
return instance;
}
});
return fn.apply(null, args);
}
return {
constant: constant,
factory: factory,
invoke: invoke
};
}
// App
function launchRocket() {
console.log('Launching rocket at '+new Date());
}
function BigRedButton(buttonAction) {
this.action = buttonAction;
}
BigRedButton.prototype.openLid = function() {
this._open = true;
};
BigRedButton.prototype.closeLid = function() {
this._open = false;
};
BigRedButton.prototype.push = function() {
if (this._open) {
this.action();
}
};
function spaceProgram(button) {
button.openLid();
button.push();
button.closeLid();
}
var injector = createInjector();
injector.factory('button', function(buttonAction) {
return new BigRedButton(buttonAction);
});
injector.constant('buttonAction', launchRocket);
injector.invoke(spaceProgram);
all we do in that factory function is call the BigRedButton constructor, passing in the same arguments.
Can we get rid of this unnecessary wrapper?
injector.factory('button', function(buttonAction) {
return new BigRedButton(buttonAction);
});injector.service('button', BigRedButton);
function createInjector() {
var instanceCache = {};
var providerCache = {};
function constant(name, value) {
instanceCache[name] = value;
}
function factory(name, factoryFn) {
providerCache[name] = factoryFn;
}
function service(name, Constructor) {
factory(name, function() {
// todo
});
}
function invoke(fn) {
//...
return fn.apply(null, args);
}
return {
constant: constant,
factory: factory,
service: service, //*
invoke: invoke
};
}function createInjector() {
var instanceCache = {};
var providerCache = {};
function constant(name, value) {
instanceCache[name] = value;
}
function factory(name, factoryFn) {
providerCache[name] = factoryFn;
}
function service(name, Constructor) {
factory(name, function() {
var instance = Object.create(Constructor.prototype);
});
}
function invoke(fn) {
//...
return fn.apply(null, args);
}
return {
constant: constant,
factory: factory,
service: service,
invoke: invoke
};
}function createInjector() {
var instanceCache = {};
var providerCache = {};
function constant(name, value) {
instanceCache[name] = value;
}
function factory(name, factoryFn) {
providerCache[name] = factoryFn;
}
function service(name, Constructor) {
factory(name, function() {
var instance = Object.create(Constructor.prototype);
invoke(Constructor, instance);
return instance;
});
}
function invoke(fn, self) { // *
//...
return fn.apply(self, args); // *
}
return {
constant: constant,
factory: factory,
service: service,
invoke: invoke
};
}// Framework
function createInjector() {
var instanceCache = {};
var providerCache = {};
function constant(name, value) {
instanceCache[name]= value;
}
function factory(name, factoryFn) {
providerCache[name] = factoryFn;
}
function service(name, Constructor) {
factory(name, function() {
var instance = Object.create(Constructor.prototype);
invoke(Constructor, instance);
return instance;
});
}
function invoke(fn, self) {
var argsString = fn.toString()
.match(/^function\s*[^\(]*\(([^\)]*)\)/)[1];
var argNames = argsString.split(',').map(function(argName) {
return argName.replace(/\s*/g, '');
});
var args = argNames.map(function(argName) {
if (instanceCache.hasOwnProperty(argName)) {
return instanceCache[argName];
} else if (providerCache.hasOwnProperty(argName)) {
var provider = providerCache[argName];
var instance = invoke(provider);
instanceCache[argName]= instance;
return instance;
}
});
return fn.apply(self, args);
}
return {
constant: constant,
factory: factory,
service: service,
invoke: invoke
};
}
// App
function launchRocket() {
console.log('Launching rocket at '+new Date());
}
function BigRedButton(buttonAction) {
this.action = buttonAction;
}
BigRedButton.prototype.openLid = function() {
this._open = true;
};
BigRedButton.prototype.closeLid = function() {
this._open = false;
};
BigRedButton.prototype.push = function() {
if (this._open) {
this.action();
}
};
function spaceProgram(button) {
button.openLid();
button.push();
button.closeLid();
}
var injector = createInjector();
injector.service('button', BigRedButton);
injector.constant('buttonAction', launchRocket);
injector.invoke(spaceProgram);