In AngularJS these two controller declarations are equivalent:
function BlahCtrl($scope, $http) { ... }
function BlahCtrl($http, $scope) { ... }
Both $http
and $scope
will be the correct variables no matter what order they are in. i.e. the variable named $http
will always be passed an instance of the $http
service.
How does Angular know which objects to pass in and in what order? I thought this kind of reflection was not possible with javascript.
If you call toString
on a function, you get the js declaration of that function:
function a(b,c) {}
a.toString(); // "function a(b,c){}"
then you can parse the string for the order of arguments.
Some investigation into the angular source code confirms this:
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
arg.replace(FN_ARG, function(all, underscore, name){
$inject.push(name);
});
});
fn.$inject = $inject;
}
}
They stringify the function, then extract the arguments with a regular expression and store them in an array.
jsFiddle showing how this all works.
Although I don't know how they do it in, there is a simple way to do it.
Everything in JS has toString()
method. For functions, it shows the source code of that particular function (for in-built functions, you may get something like function() { [native code] }
).
Let us find the first (
and the first )
, which enclose the function's arguments. Then, let's strip the whitespaces and split the arguments by ,
. Voila, we get an array of argument names.
function a($scope, $http) { };
function b($http, $scope) { };
function getParameterList(f) {
var s = f.toString();
var start = s.indexOf('(');
var end = s.indexOf(')');
s = s.substring(start + 1, end);
return s.replace(/ /g,'').split(',');
}
So let's test it:
var aParams = getParameterList(a);
var bParams = getParameterList(b);
alert(aParams[0]); // $scope
alert(aParams[1]); // $http
alert(bParams[0]); // $http
alert(bParams[1]); // $scope
A fiddle: http://jsfiddle.net/jYPB8/
However, note that this behaviour of toString()
is defined in Function.prototype
and may be redefined - in which case, this algorithm won't work.
So while this may not be the actual solution you were looking for, I wanted to show you that this kind of reflection is possible in JavaScript, and it's actually very simple to do :)