diff options
| author | Misko Hevery | 2011-04-18 16:33:30 -0700 | 
|---|---|---|
| committer | Misko Hevery | 2011-06-08 15:21:31 -0700 | 
| commit | 8cad231bd219eddd518de8b8bd040d3f12f08d17 (patch) | |
| tree | 75016fa6de683a877916f45a0fd06b1d0f312231 /src | |
| parent | 0e17ade959cc77369dc102d180e43be2af68505a (diff) | |
| download | angular.js-8cad231bd219eddd518de8b8bd040d3f12f08d17.tar.bz2 | |
Refactor injector to have invoke method for speed reasons
Diffstat (limited to 'src')
| -rw-r--r-- | src/AngularPublic.js | 2 | ||||
| -rw-r--r-- | src/Compiler.js | 10 | ||||
| -rw-r--r-- | src/Injector.js | 171 | ||||
| -rw-r--r-- | src/Scope.js | 4 | ||||
| -rw-r--r-- | src/apis.js | 34 | ||||
| -rw-r--r-- | src/directives.js | 4 | ||||
| -rw-r--r-- | src/widgets.js | 4 | 
7 files changed, 145 insertions, 84 deletions
| diff --git a/src/AngularPublic.js b/src/AngularPublic.js index cc901d6d..37e0a082 100644 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -18,6 +18,8 @@ angularService('$browser', function($log){  }, {$inject:['$log']});  extend(angular, { +  // disabled for now until we agree on public name +  //'annotate': annotate,    'element': jqLite,    'compile': compile,    'scope': createScope, diff --git a/src/Compiler.js b/src/Compiler.js index d5eeae79..08c76eea 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -34,7 +34,7 @@ Template.prototype = {      forEach(this.inits, function(fn) {        queue.push(function() {          childScope.$tryEval(function(){ -          return childScope.$service(fn, childScope, element); +          return childScope.$service.invoke(childScope, fn, [element]);          }, element);        });      }); @@ -49,9 +49,11 @@ Template.prototype = {    }, -  addInit:function(init) { -    if (init) { -      this.inits.push(init); +  addInit:function(linkingFn) { +    if (linkingFn) { +      if (!linkingFn.$inject) +        linkingFn.$inject = []; +      this.inits.push(linkingFn);      }    }, diff --git a/src/Injector.js b/src/Injector.js index 77d25a77..9e1464e9 100644 --- a/src/Injector.js +++ b/src/Injector.js @@ -4,81 +4,114 @@   * @function   *   * @description - * Creates an inject function that can be used for dependency injection. - * (See {@link guide/dev_guide.di dependency injection}) + * Creates an injector function that can be used for retrieving services as well as for + * dependency injection (see {@link guide/dev_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}. + * Angular creates an injector automatically for the root scope and it is available as the + * {@link angular.scope.$service $service} property. Creation of the injector automatically creates + * all of the `$eager` {@link angular.service services}.   * - * @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: `function(value, scope, args...)`: + * @param {Object=} [factoryScope={}] `this` for the service factory function. + * @param {Object.<string, function()>=} [factories=angular.service] Map of service factory + *     functions. + * @param {Object.<string, function()>=} [instanceCache={}] Place where instances of services are + *     saved for reuse. Can also be used to override services specified by `serviceFactory` + *     (useful in tests). + * @returns {function()} Injector function:   * - *     * `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). + *   * `injector(serviceName)`: + *     * `serviceName` - `{string=}` - name of the service to retrieve.   * - *   #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. + * The injector function also has these properties:   * + *   * an `invoke` property which can be used to invoke methods with dependency-injected arguments. + *    `injector.invoke(self, fn, curryArgs)` + *     * `self` -  "`this`" to be used when invoking the function. + *     * `fn` - the function to be invoked. The function may have the `$inject` property which + *        lists the set of arguments which should be auto injected + *        (see {@link guide.di dependency injection}). + *     * `curryArgs(array)` - optional array of arguments to pass to function invocation after the + *        injection arguments (also known as curry arguments or currying). + *   * an `eager` property which is used to initialize the eager services. + *     `injector.eager()`   */ -function createInjector(providerScope, providers, cache) { -  providers = providers || angularService; -  cache = cache || {}; -  providerScope = providerScope || {}; -  return function inject(value, scope, args){ -    var returnValue, provider; -    if (isString(value)) { -      if (!(value in cache)) { -        provider = providers[value]; -        if (!provider) throw "Unknown provider for '"+value+"'."; -        cache[value] = inject(provider, providerScope); -      } -      returnValue = cache[value]; -    } else if (isArray(value)) { -      returnValue = []; -      forEach(value, function(name) { -        returnValue.push(inject(name)); -      }); -    } else if (isFunction(value)) { -      returnValue = inject(injectionArgs(value)); -      returnValue = value.apply(scope, concat(returnValue, arguments, 2)); -    } else if (isObject(value)) { -      forEach(providers, function(provider, name){ -        if (provider.$eager) -          inject(name); +function createInjector(factoryScope, factories, instanceCache) { +  factories = factories || angularService; +  instanceCache = instanceCache || {}; +  factoryScope = factoryScope || {}; +  injector.invoke = invoke; -        if (provider.$creation) -          throw new Error("Failed to register service '" + name + -              "': $creation property is unsupported. Use $eager:true or see release notes."); -      }); -    } else { -      returnValue = inject(providerScope); +  injector.eager = function(){ +    forEach(factories, function(factory, name){ +      if (factory.$eager) +        injector(name); + +      if (factory.$creation) +        throw new Error("Failed to register service '" + name + +        "': $creation property is unsupported. Use $eager:true or see release notes."); +    }); +  }; +  return injector; + +  function injector(value){ +    if (!(value in instanceCache)) { +      var factory = factories[value]; +      if (!factory) throw Error("Unknown provider for '"+value+"'."); +      instanceCache[value] = invoke(factoryScope, factory);      } -    return returnValue; +    return instanceCache[value];    }; -} -function injectService(services, fn) { -  return extend(fn, {$inject:services}); +  function invoke(self, fn, args){ +    args = args || []; +    var injectNames = injectionArgs(fn); +    var i = injectNames.length; +    while(i--) { +      args.unshift(injector(injectNames[i])); +    } +    return fn.apply(self, args); +  }  } -function injectUpdateView(fn) { -  return injectService(['$updateView'], fn); +/*NOT_PUBLIC_YET + * @ngdoc function + * @name angular.annotate + * @function + * + * @description + * Annotate the function with injection arguments. This is equivalent to setting the `$inject` + * property as described in {@link guide.di dependency injection}. + * + * <pre> + * var MyController = angular.annotate('$location', function($location){ ... }); + * </pre> + * + * is the same as + * + * <pre> + * var MyController = function($location){ ... }; + * MyController.$inject = ['$location']; + * </pre> + * + * @param {String|Array} serviceName... zero or more service names to inject into the + *     `annotatedFunction`. + * @param {function} annotatedFunction function to annotate with `$inject` + *     functions. + * @returns {function} `annotatedFunction` + */ +function annotate(services, fn) { +  if (services instanceof Array) { +    fn.$inject = services; +    return fn; +  } else { +    var i = 0, +        length = arguments.length - 1, // last one is the destination function +        $inject = arguments[length].$inject = []; +    for (; i < length; i++) { +      $inject.push(arguments[i]); +    } +    return arguments[length]; // return the last one +  }  }  function angularServiceInject(name, fn, inject, eager) { @@ -89,12 +122,12 @@ function angularServiceInject(name, fn, inject, 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 + * extracting all arguments which and assuming that they are the   * injection names.   */  var FN_ARGS = /^function\s*[^\(]*\(([^\)]*)\)/;  var FN_ARG_SPLIT = /,/; -var FN_ARG = /^\s*(((\$?).+?)(_?))\s*$/; +var FN_ARG = /^\s*(.+?)\s*$/;  var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;  function injectionArgs(fn) {    assertArgFn(fn); @@ -103,12 +136,8 @@ function injectionArgs(fn) {      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 +      arg.replace(FN_ARG, function(all, name){ +        args.push(name);        });      });    } diff --git a/src/Scope.js b/src/Scope.js index 1ab583e8..b2b8cdb4 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -472,7 +472,7 @@ function createScope(parent, providers, instanceCache) {          forEach(Class.prototype, function(fn, name){            instance[name] = bind(instance, fn);          }); -        instance.$service.apply(instance, concat([Class, instance], arguments, 1)); +        instance.$service.invoke(instance, Class, slice.call(arguments, 1, arguments.length));          //TODO: backwards compatibility hack, remove when we don't depend on init methods          if (isFunction(Class.prototype.init)) { @@ -525,7 +525,7 @@ function createScope(parent, providers, instanceCache) {       * @param {string} serviceId String ID of the service to return.       * @returns {*} Value, object or function returned by the service factory function if any.       */ -    (instance.$service = createInjector(instance, providers, instanceCache))(); +    instance.$service = createInjector(instance, providers, instanceCache);    }    $log = instance.$service('$log'); diff --git a/src/apis.js b/src/apis.js index 11fa9838..1e1bcae1 100644 --- a/src/apis.js +++ b/src/apis.js @@ -807,6 +807,16 @@ var angularFunction = {    }  }; +/** + * Computes a hash of an 'obj'. + * Hash of a: + *  string is string + *  number is number as string + *  object is either call $hashKey function on object or assign unique hashKey id. + *  + * @param obj + * @returns {String} hash string such that the same input will have the same hash string + */  function hashKey(obj) {    var objType = typeof obj;    var key = obj; @@ -821,17 +831,37 @@ function hashKey(obj) {    return objType + ':' + key;  } +/** + * HashMap which can use objects as keys + */  function HashMap(){}  HashMap.prototype = { +  /** +   * Store key value pair +   * @param key key to store can be any type +   * @param value value to store can be any type +   * @returns old value if any +   */    put: function(key, value) {      var _key = hashKey(key);      var oldValue = this[_key];      this[_key] = value;      return oldValue;    }, +   +  /** +   * @param key +   * @returns the value for the key +   */    get: function(key) {      return this[hashKey(key)];    }, +   +  /** +   * Remove the key/value pair +   * @param key +   * @returns value associated with key before it was removed +   */    remove: function(key) {      var _key = hashKey(key);      var value = this[_key]; @@ -852,8 +882,6 @@ defineApi('Array', [angularGlobal, angularCollection, angularArray]);  defineApi('Object', [angularGlobal, angularCollection, angularObject]);  defineApi('String', [angularGlobal, angularString]);  defineApi('Date', [angularGlobal, angularDate]); -// TODO: enable and document this API -//defineApi('HashMap', [HashMap]);  //IE bug -angular['Date']['toString'] = angularDate['toString']; +angular.Date.toString = angularDate.toString;  defineApi('Function', [angularGlobal, angularCollection, angularFunction]); diff --git a/src/directives.js b/src/directives.js index 680e8a72..34a1b27d 100644 --- a/src/directives.js +++ b/src/directives.js @@ -511,7 +511,7 @@ angularDirective("ng:bind-attr", function(expression){   * TODO: maybe we should consider allowing users to control event propagation in the future.   */  angularDirective("ng:click", function(expression, element){ -  return injectUpdateView(function($updateView, element){ +  return annotate('$updateView', function($updateView, element){      var self = this;      element.bind('click', function(event){        self.$tryEval(expression, element); @@ -561,7 +561,7 @@ angularDirective("ng:click", function(expression, element){     </doc:example>   */  angularDirective("ng:submit", function(expression, element) { -  return injectUpdateView(function($updateView, element) { +  return annotate('$updateView', function($updateView, element) {      var self = this;      element.bind('submit', function(event) {        self.$tryEval(expression, element); diff --git a/src/widgets.js b/src/widgets.js index cc6d48b9..4245f99c 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -522,7 +522,7 @@ function radioInit(model, view, element) {      </doc:example>   */  function inputWidget(events, modelAccessor, viewAccessor, initFn, textBox) { -  return injectService(['$updateView', '$defer'], function($updateView, $defer, element) { +  return annotate('$updateView', '$defer', function($updateView, $defer, element) {      var scope = this,          model = modelAccessor(scope, element),          view = viewAccessor(scope, element), @@ -1097,7 +1097,7 @@ angularWidget('ng:view', function(element) {    if (!element[0]['ng:compiled']) {      element[0]['ng:compiled'] = true; -    return injectService(['$xhr.cache', '$route'], function($xhr, $route, element){ +    return annotate('$xhr.cache', '$route', function($xhr, $route, element){        var parentScope = this,            childScope; | 
