Demystifying AngularJS' dependency injection
2nd February 2014 | by Adam Beres-Deak | javascript, angularjs, dependency injection
Many people think that it is some kind of magic, how AngularJS resolves dependencies given as function arguments. But it isn't any magic. I'll show you, how they achieve it. This technique can be used everywhere, not just with AngularJS.
If you don't know what does dependency injection look like with AngularJS, here is an example:
<div ng-controller="GreetCtrl">
Hello !
</div>
<script>
function GreetCtrl($scope, $rootScope) {
$scope.name = 'World';
$rootScope.department = 'Angular';
}
</script>
We just have to put $scope
and $rootScope
into the function arguments, and when Angular calls the controller (GreetCtrl
) it knows with parameters it has to call the function.
How do they do it?
It's fairly easy in JavaScript to get the source code of the every function. We just have to call the .toString()
method of the function objects.
function sum(x, y) {
alert(x + y);
}
console.log(sum.toString()); // prints the complete code of the function
Here we go. We just have to find a way to extract the argument list of our function.
// The following simplified code is partly taken from the AngularJS source code:
// https://github.com/angular/angular.js/blob/master/src/auto/injector.js#L63
function inject(fn, variablesToInject) {
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
if (typeof fn === 'function' && fn.length) {
var fnText = fn.toString(); // getting the source code of the function
fnText = fnText.replace(STRIP_COMMENTS, ''); // stripping comments like function(/*string*/ a) {}
var matches = fnText.match(FN_ARGS); // finding arguments
var argNames = matches[1].split(FN_ARG_SPLIT); // finding each argument name
var newArgs = [];
for (var i = 0, l = argNames.length; i < l; i++) {
var argName = argNames[i].trim();
if (!variablesToInject.hasOwnProperty(argName)) {
// the argument cannot be injected
throw new Error("Unknown argument: '" + argName + "'. This cannot be injected.");
}
newArgs.push(variablesToInject[argName]);
}
fn.apply(window, newArgs);
}
}
function sum(x, y) {
console.log(x + y);
}
inject(sum, {
x: 5,
y: 6
}); // should print 11
inject(sum, {
x: 13,
y: 45
}); // should print 58
inject(sum, {
x: 33,
z: 1 // we are missing 'y'
}); // should throw an error: Unknown argument: 'y'. This cannot be injected.
So it's really not a big deal, is it?
Latest blog posts
Displaying icons with custom elements 14th October 2015
Plain JavaScript event delegation 26th January 2015
After the first year of blogging - what happened on my blog in 2014? 1st January 2015
Better webfont loading with using localStorage and providing WOFF2 support 18th December 2014