diff options
| -rw-r--r-- | src/auto/injector.js | 142 | ||||
| -rw-r--r-- | test/auto/injectorSpec.js | 27 | 
2 files changed, 131 insertions, 38 deletions
diff --git a/src/auto/injector.js b/src/auto/injector.js index 250f05c5..49247ff4 100644 --- a/src/auto/injector.js +++ b/src/auto/injector.js @@ -42,19 +42,32 @@ var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;  var FN_ARG_SPLIT = /,/;  var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;  var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; -function inferInjectionArgs(fn) { -  assertArgFn(fn); -  if (!fn.$inject) { -    var args = fn.$inject = []; -    var fnText = fn.toString().replace(STRIP_COMMENTS, ''); -    var argDecl = fnText.match(FN_ARGS); -    forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ -      arg.replace(FN_ARG, function(all, underscore, name){ -        args.push(name); +function annotate(fn) { +  var $inject, +      fnText, +      argDecl, +      last; + +  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; +    } +  } else if (isArray(fn)) { +    last = fn.length - 1; +    assertArgFn(fn[last], 'fn') +    $inject = fn.slice(0, last); +  } else { +    assertArgFn(fn, 'fn', true);    } -  return fn.$inject; +  return $inject;  }  /////////////////////////////////////// @@ -152,6 +165,87 @@ function inferInjectionArgs(fn) {   * @returns {Object} new instance of `Type`.   */ +/** + * @ngdoc method + * @name angular.module.AUTO.$injector#annotate + * @methodOf angular.module.AUTO.$injector + * + * @description + * Returns an array of service names which the function is requesting for injection. This API is used by the injector + * to determine which services need to be injected into the function when the function is invoked. There are three + * ways in which the function can be annotated with the needed dependencies. + * + * # Argument names + * + * The simplest form is to extract the dependencies from the arguments of the function. This is done by converting + * the function into a string using `toString()` method and extracting the argument names. + * <pre> + *   // Given + *   function MyController($scope, $route) { + *     // ... + *   } + * + *   // Then + *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); + * </pre> + * + * This method does not work with code minfication / obfuscation. For this reason the following annotation strategies + * are supported. + * + * # The `$injector` property + * + * If a function has an `$inject` property and its value is an array of strings, then the strings represent names of + * services to be injected into the function. + * <pre> + *   // Given + *   var MyController = function(obfuscatedScope, obfuscatedRoute) { + *     // ... + *   } + *   // Define function dependencies + *   MyController.$inject = ['$scope', '$route']; + * + *   // Then + *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); + * </pre> + * + * # The array notation + * + * It is often desirable to inline Injected functions and that's when setting the `$inject` property is very + * inconvenient. In these situations using the array notation to specify the dependencies in a way that survives + * minification is a better choice: + * + * <pre> + *   // We wish to write this (not minification / obfuscation safe) + *   injector.invoke(function($compile, $rootScope) { + *     // ... + *   }); + * + *   // We are forced to write break inlining + *   var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { + *     // ... + *   }; + *   tmpFn.$inject = ['$compile', '$rootScope']; + *   injector.invoke(tempFn); + * + *   // To better support inline function the inline annotation is supported + *   injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { + *     // ... + *   }]); + * + *   // Therefore + *   expect(injector.annotate( + *      ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) + *    ).toEqual(['$compile', '$rootScope']); + * </pre> + * + * @param {function|Array.<string|Function>} fn Function for which dependent service names need to be retrieved as described + *   above. + * + * @returns {Array.<string>} The names of the services which the function requires. + */ + + +  /**   * @ngdoc object @@ -454,23 +548,11 @@ function createInjector(modulesToLoad) {      function invoke(fn, self, locals){        var args = [], -          $inject, -          length, +          $inject = annotate(fn), +          length, i,            key; -      if (typeof fn == 'function') { -        $inject = inferInjectionArgs(fn); -        length = $inject.length; -      } else { -        if (isArray(fn)) { -          $inject = fn; -          length = $inject.length - 1; -          fn = $inject[length]; -        } -        assertArgFn(fn, 'fn'); -      } - -      for(var i = 0; i < length; i++) { +      for(i = 0, length = $inject.length; i < length; i++) {          key = $inject[i];          args.push(            locals && locals.hasOwnProperty(key) @@ -478,6 +560,11 @@ function createInjector(modulesToLoad) {            : getService(key, path)          );        } +      if (!fn.$inject) { +        // this means that we must be an array. +        fn = fn[length]; +      } +        // Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke        switch (self ? -1 : args.length) { @@ -510,7 +597,8 @@ function createInjector(modulesToLoad) {      return {        invoke: invoke,        instantiate: instantiate, -      get: getService +      get: getService, +      annotate: annotate      };    }  } diff --git a/test/auto/injectorSpec.js b/test/auto/injectorSpec.js index 83bd3d1f..33fecac6 100644 --- a/test/auto/injectorSpec.js +++ b/test/auto/injectorSpec.js @@ -123,11 +123,11 @@ describe('injector', function() {      it('should return $inject', function() {        function fn() {}        fn.$inject = ['a']; -      expect(inferInjectionArgs(fn)).toBe(fn.$inject); -      expect(inferInjectionArgs(function() {})).toEqual([]); -      expect(inferInjectionArgs(function () {})).toEqual([]); -      expect(inferInjectionArgs(function  () {})).toEqual([]); -      expect(inferInjectionArgs(function /* */ () {})).toEqual([]); +      expect(annotate(fn)).toBe(fn.$inject); +      expect(annotate(function() {})).toEqual([]); +      expect(annotate(function () {})).toEqual([]); +      expect(annotate(function  () {})).toEqual([]); +      expect(annotate(function /* */ () {})).toEqual([]);      }); @@ -142,43 +142,48 @@ describe('injector', function() {                   */            _c,            /* {some type} */ d) { extraParans();} -      expect(inferInjectionArgs($f_n0)).toEqual(['$a', 'b_', '_c',  'd']); +      expect(annotate($f_n0)).toEqual(['$a', 'b_', '_c',  'd']);        expect($f_n0.$inject).toEqual(['$a', 'b_', '_c',  'd']);      });      it('should strip leading and trailing underscores from arg name during inference', function() {        function beforeEachFn(_foo_) { /* foo = _foo_ */ }; -      expect(inferInjectionArgs(beforeEachFn)).toEqual(['foo']); +      expect(annotate(beforeEachFn)).toEqual(['foo']);      });      it('should handle no arg functions', function() {        function $f_n0() {} -      expect(inferInjectionArgs($f_n0)).toEqual([]); +      expect(annotate($f_n0)).toEqual([]);        expect($f_n0.$inject).toEqual([]);      });      it('should handle no arg functions with spaces in the arguments list', function() {        function fn( ) {} -      expect(inferInjectionArgs(fn)).toEqual([]); +      expect(annotate(fn)).toEqual([]);        expect(fn.$inject).toEqual([]);      });      it('should handle args with both $ and _', function() {        function $f_n0($a_) {} -      expect(inferInjectionArgs($f_n0)).toEqual(['$a_']); +      expect(annotate($f_n0)).toEqual(['$a_']);        expect($f_n0.$inject).toEqual(['$a_']);      });      it('should throw on non function arg', function() {        expect(function() { -        inferInjectionArgs({}); +        annotate({});        }).toThrow();      }); + + +    it('should publish annotate API', function() { +      expect(injector.annotate).toBe(annotate); +    });    });  | 
