Dependency Injection
(in AngularJS)
What is Dependency Injection?
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.
Dependency Injection - Why?
- Decoupled code components
- Organized & structured code
- Testing
What are we going to do?
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
Show Time!
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();it works!
but something feels wrong...
- BigRedButton coupled with launchRocket
- spaceProgram coupled with BigRedButton
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();
}Show Time!
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));
Great!
But what if we had larger application?
it's difficult to maintain a large dependency graph manually
:(
Injector
Injector
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 ?
Function.prototype.toString()
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.
What the Func?!
/^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
Show Time!
// 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);
Cool, but...
we still have to manually construct the dependency between the big red button and the launchRocket function
new BigRedButton(launchRocket)Factories
Instead of giving the injector the constant value directly, let's give it a function that returns that value!
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
};
}Show Time!
// 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);
Cool, but...
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);
});Services
A service takes a constructor function instead of a plain old function.
In addition to invoking that function with dependency injection, it also sets up a new object with the function's prototype chain.
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
};
}What we need to do
- Construct a new object with the prototype chain of the given constructor
- Invoke the given constructor with dependency injection
Show Time!
// 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);
Questions?
AngularJS Dependency Injection
By eladdo92
AngularJS Dependency Injection
- 503