diff options
| author | Misko Hevery | 2011-02-16 19:18:35 -0500 | 
|---|---|---|
| committer | Misko Hevery | 2011-02-18 13:14:07 -0800 | 
| commit | 7d4aee31bb202e9b050fc15c56cfc33c46b9eaf5 (patch) | |
| tree | 3c503f5ac9104983d3ed665da76a464142e94a53 | |
| parent | 7a54d2791ff4de5808970f3862114f84121115b5 (diff) | |
| download | angular.js-7d4aee31bb202e9b050fc15c56cfc33c46b9eaf5.tar.bz2 | |
Auto create $inject property form the argument names. Any arg starting with $ or _ will be injected
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | src/Angular.js | 9 | ||||
| -rw-r--r-- | src/Injector.js | 74 | ||||
| -rw-r--r-- | test/InjectorSpec.js | 49 | 
4 files changed, 110 insertions, 23 deletions
| diff --git a/CHANGELOG.md b/CHANGELOG.md index 57665b9a..ef08e2c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@  ### API  - rewrite of JQuery lite implementation for better supports operations on multiple nodes when    matched by a selector. +- Infer DI dependencies from function signature. http://docs.angularjs.org/#!guide.di  ### Breaking changes  - Removed the $init() method after the compilation. The old way of compiling the DOM element was diff --git a/src/Angular.js b/src/Angular.js index 2d4b1671..05b9989e 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1077,10 +1077,15 @@ function bindJQuery(){  /**   * throw error of the argument is falsy.   */ -function assertArg(arg, name) { +function assertArg(arg, name, reason) {    if (!arg) { -    var error = new Error("Argument '" + name + "' is required"); +    var error = new Error("Argument '" + (name||'?') + "' is " + +        (reason || "required"));      if (window.console) window.console.log(error.stack);      throw error;    }  }; + +function assertArgFn(arg, name) { +  assertArg(isFunction(arg, name, 'not a function')); +}; diff --git a/src/Injector.js b/src/Injector.js index 295928bf..a5a0b540 100644 --- a/src/Injector.js +++ b/src/Injector.js @@ -5,37 +5,41 @@   *   * @description   * Creates an inject function that can be used for dependency injection. + * (See {@link guide.di dependency injection}) + * + * The inject function can be used for retrieving service instances or for calling any function + * which has the $inject property so that the services can be automatically provided. Angular + * creates an injection function automatically for the root scope and it is available as + * {@link angular.scope.$service $service}.   *   * @param {Object=} [providerScope={}] provider's `this`   * @param {Object.<string, function()>=} [providers=angular.service] Map of provider (factory)   *     function.   * @param {Object.<string, function()>=} [cache={}] Place where instances are saved for reuse. Can   *     also be used to override services speciafied by `providers` (useful in tests). - * @returns {function()} Injector function. + * @returns + *   {function()} Injector function: `function(value, scope, args...)`: + * + *     * `value` - `{string|array|function}` + *     * `scope(optional=rootScope)` -  optional function "`this`" when `value` is type `function`. + *     * `args(optional)` - optional set of arguments to pass to function after injection arguments. + *        (also known as curry arguments or currying). + * + *   #Return value of `function(value, scope, args...)` + *   The injector function return value depended on the type of `value` argument: + * + *     * `string`: return an instance for the injection key. + *     * `array` of keys: returns an array of instances for those keys. (see `string` above.) + *     * `function`: look at `$inject` property of function to determine instances to inject + *       and then call the function with instances and `scope`. Any additional arguments + *       (`args`) are appended to the function arguments. + *     * `none`: initialize eager providers.   * - * @TODO These docs need a lot of work. Specifically the returned function should be described in - *     great detail + we need to provide some examples.   */  function createInjector(providerScope, providers, cache) {    providers = providers || angularService;    cache = cache || {};    providerScope = providerScope || {}; -  /** -   * injection function -   * @param value: string, array, object or function. -   * @param scope: optional function "this" -   * @param args: optional arguments to pass to function after injection -   *              parameters -   * @returns depends on value: -   *   string: return an instance for the injection key. -   *   array of keys: returns an array of instances. -   *   function: look at $inject property of function to determine instances -   *             and then call the function with instances and `scope`. Any -   *             additional arguments (`args`) are appended to the function -   *             arguments. -   *   object: initialize eager providers and publish them the ones with publish here. -   *   none:   same as object but use providerScope as place to publish. -   */    return function inject(value, scope, args){      var returnValue, provider;      if (isString(value)) { @@ -51,7 +55,7 @@ function createInjector(providerScope, providers, cache) {          returnValue.push(inject(name));        });      } else if (isFunction(value)) { -      returnValue = inject(value.$inject || []); +      returnValue = inject(injectionArgs(value));        returnValue = value.apply(scope, concat(returnValue, arguments, 2));      } else if (isObject(value)) {        forEach(providers, function(provider, name){ @@ -80,3 +84,33 @@ function injectUpdateView(fn) {  function angularServiceInject(name, fn, inject, eager) {    angularService(name, fn, {$inject:inject, $eager:eager});  } + + +/** + * @returns the $inject property of function. If not found the + * the $inject is computed by looking at the toString of function and + * extracting all arguments which start with $ or end with _ as the + * injection names. + */ +var FN_ARGS = /^function [^\(]*\(([^\)]*)\)/; +var FN_ARG_SPLIT = /,/; +var FN_ARG = /^\s*(((\$?).+?)(_?))\s*$/; +var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; +function injectionArgs(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, name, injectName, $, _){ +        assertArg(args, name, 'after non-injectable arg'); +        if ($ || _) +          args.push(injectName); +        else +          args = null; // once we reach an argument which is not injectable then ignore +      }); +    }); +  } +  return fn.$inject; +}; diff --git a/test/InjectorSpec.js b/test/InjectorSpec.js index 765003cf..c876f188 100644 --- a/test/InjectorSpec.js +++ b/test/InjectorSpec.js @@ -53,9 +53,56 @@ describe('injector', function(){    it('should autostart eager services', function(){      var log = ''; -    providers('eager', function(){log += 'eager;'; return 'foo'}, {$eager: true}); +    providers('eager', function(){log += 'eager;'; return 'foo';}, {$eager: true});      inject();      expect(log).toEqual('eager;');      expect(inject('eager')).toBe('foo');    }); + +  describe('annotation', function(){ +    it('should return $inject', function(){ +      function fn(){}; +      fn.$inject = ['a']; +      expect(injectionArgs(fn)).toBe(fn.$inject); +      expect(injectionArgs(function(){})).toEqual([]); +    }); + +    it('should create $inject', function(){ +      // keep the multi-line to make sure we can handle it +      function $f_n0( +          $a, // x, <-- looks like an arg but it is a comment +          b_, /* z, <-- looks like an arg but it is a +                 multi-line comment +                 function (a, b){} +                 */ +          /* {some type} */ c){ extraParans();}; +      expect(injectionArgs($f_n0)).toEqual(['$a', 'b']); +      expect($f_n0.$inject).toEqual(['$a', 'b']); +    }); + +    it('should handle no arg functions', function(){ +      function $f_n0(){}; +      expect(injectionArgs($f_n0)).toEqual([]); +      expect($f_n0.$inject).toEqual([]); +    }); + +    it('should handle args with both $ and _', function(){ +      function $f_n0($a_){}; +      expect(injectionArgs($f_n0)).toEqual(['$a']); +      expect($f_n0.$inject).toEqual(['$a']); +    }); + +    it('should throw on non function arg', function(){ +      expect(function(){ +        injectionArgs({}); +      }).toThrow(); +    }); + +    it('should throw on injectable after non-injectable arg', function(){ +      expect(function(){ +        injectionArgs(function($a, b_, nonInject, d_){}); +      }).toThrow(); +    }); + +  });  }); | 
