0

The recommended way of passing a function to various AngularJS methods is by using the syntax that's called, in AngularJS docs, the array notation.

You should do this:

app.controller("MyCtrl", ['$scope', function($scope){
$scope.someData = "Array notation";
}]);

instead of this:

app.controller("MyCtrl", function($scope){
$scope.someData = "non-array notation";
});

because of the way AngularJS dependency injection and minification works.

I'd like to know if the first way of passing a function as parameter is a syntax mentioned in JavaScript standard? I haven't managed to find anything about 'array notation' in this context on the web.

2
  • It's not mentioned in JS because it's just a convention - but an easy one to follow. In the corresponding code, it's enough to check the type of argument: if it's just a function, attempt to extract the dependency by analyzing its source code. Commented Sep 6, 2015 at 14:59
  • No, you can pass a bunch of strings before the function and angular will handle them. Commented Sep 6, 2015 at 15:05

2 Answers 2

2

It's just a convention - but it's thought-out pretty well, hence been used throughout Angular. See, a function - a module - can have just a single dependency (as in your example), or many of them (starting from two, right), or no dependency at all. So we need some solution that answers all the cases.

The naive approach is specifying all the deps as function arguments (your second example). Now, it's possible to extract (and inject) them by analyzing the function source code. Pro: absolute minimum code to write (you'd have to specify all the deps' names anyway). Cons: 1) based on reflection (which is never fast), 2) breaks when the script is minified (and all the params' names are transformed).

These cons are bad enough so there just has to be another way. We wouldn't want to get rid of the arguments list, though (those deps still had to be addressed within the function somehow, right?). But now it's clear that the single list isn't enough - it has to be duplicated somewhere.

And this is where Array - an ordered sequence of elements - comes quite handy. Now the injector only has to separate the last element of that array to get the full deps list. Those are strings, not variables, so they won't be modified by minifier. What's even better, now we don't have to analyze the signature, so the injector works a bit faster.


From theory to practice: this is how those two approaches are implemented in Angular 1.x DI module:

function annotate(fn, strictDi, name) {
  var $inject,
      fnText,
      argDecl,
      last;

  if (typeof fn === 'function') {
    // first approach: only function is passed, we need to analyze the args list  
    if (!($inject = fn.$inject)) {
      $inject = [];
      if (fn.length) {
        if (strictDi) {
          if (!isString(name) || !name) {
            name = fn.name || anonFn(fn);
          }
          throw $injectorMinErr('strictdi',
            '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
        }
        // get rid of comments, it's possible to have those inside `()`
        fnText = fn.toString().replace(STRIP_COMMENTS, '');
        // extract arguments
        argDecl = fnText.match(FN_ARGS);
        // push those into injector
        forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
          arg.replace(FN_ARG, function(all, underscore, name) {
            $inject.push(name);
          });
        });
        // ... and that took some time
      }
      fn.$inject = $inject;
    }
  } else if (isArray(fn)) {
    // second approach: an array is passed
    last = fn.length - 1;
    // make sure its last element is a function 
    assertArgFn(fn[last], 'fn');
    // use all but the last element as list of deps
    $inject = fn.slice(0, last);
    // ... and that's all, believe it or not!
  } else {
    assertArgFn(fn, 'fn', true);
  }
  return $inject;
}

As you see, the first if branch is for the old way - deps expressed as function arguments. The second (much easier to read and execute) - for deps and function placed in an array (function being the last element).

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

4 Comments

Thanks for amazing answer! I'm curious though - what might be the purpose of 'fn' in assertArgFn(fn[last], 'fn')? The method name itself, assertArgFn, leaves no doubt with regard to what it's supposed to do.
You're correct, but it's applied in different cases to different arguments. Check the source code for details; you'll see that in this particular case fn is just a name of the argument.
My point is that this funciton takes the argument 'name' and doesn't do anything with it in its body. Then what's the point of taking this argument? I must be missing something here.
It actually does - but in assertArg function (it's defined right above assertArgFn). It's used to create a proper error message.
1

It's just an array with a string and a function.

There's nothing magical or AngularJS about the notation itself. Functions can be array members just like any other data type.

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.