10

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.

0

2 Answers 2

13

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.

Sign up to request clarification or add additional context in comments.

1 Comment

My jaw just dropped. I'm suddenly inspired to write Function.prototype.callWithNamedParameters...
4

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 :)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.