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?

by Adam Beres-Deak

| Share | Tweet | Share | Share

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