mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
Refactor injector to have invoke method for speed reasons
This commit is contained in:
parent
0e17ade959
commit
8cad231bd2
11 changed files with 184 additions and 115 deletions
|
|
@ -4,6 +4,11 @@
|
|||
### Bug Fixes
|
||||
- Number filter would return incorrect value when fractional part had leading zeros.
|
||||
|
||||
### Breaking changes
|
||||
- $service now has $service.invoke for method injection ($service(self, fn) no longer works)
|
||||
- injection name inference no longer supports method curry and linking functions. Both must be
|
||||
explicitly specified using $inject property.
|
||||
|
||||
|
||||
<a name="0.9.16"><a/>
|
||||
# <angular/> 0.9.16 weather-control (2011-06-07) #
|
||||
|
|
@ -526,4 +531,4 @@ with the `$route` service
|
|||
[guide.di]: http://docs.angularjs.org/#!/guide/dev_guide.di
|
||||
[downloading]: http://docs.angularjs.org/#!/misc/downloading
|
||||
[contribute]: http://docs.angularjs.org/#!/misc/contribute
|
||||
[Jstd Scenario Adapter]: https://github.com/angular/angular.js/blob/master/src/jstd-scenario-adapter/Adapter.js
|
||||
[Jstd Scenario Adapter]: https://github.com/angular/angular.js/blob/master/src/jstd-scenario-adapter/Adapter.js
|
||||
|
|
|
|||
0
docs/src/gen-docs.js
Normal file → Executable file
0
docs/src/gen-docs.js
Normal file → Executable file
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
175
src/Injector.js
175
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);
|
||||
}
|
||||
return returnValue;
|
||||
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 instanceCache[value];
|
||||
};
|
||||
|
||||
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 injectService(services, fn) {
|
||||
return extend(fn, {$inject:services});
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
34
src/apis.js
34
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]);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,23 @@
|
|||
describe('injector', function(){
|
||||
var providers;
|
||||
var cache;
|
||||
var inject;
|
||||
var injector;
|
||||
var scope;
|
||||
|
||||
beforeEach(function(){
|
||||
providers = extensionMap({}, 'providers');
|
||||
cache = {};
|
||||
scope = {};
|
||||
inject = createInjector(scope, providers, cache);
|
||||
injector = createInjector(scope, providers, cache);
|
||||
});
|
||||
|
||||
it("should return same instance from calling provider", function(){
|
||||
providers('text', function(){ return scope.name; });
|
||||
scope.name = 'abc';
|
||||
expect(inject('text')).toEqual('abc');
|
||||
expect(injector('text')).toEqual('abc');
|
||||
expect(cache.text).toEqual('abc');
|
||||
scope.name = 'deleted';
|
||||
expect(inject('text')).toEqual('abc');
|
||||
});
|
||||
|
||||
it("should return an array of instances", function(){
|
||||
cache.a = 0;
|
||||
providers('b', function(){return 2;});
|
||||
expect(inject(['a', 'b'])).toEqual([0,2]);
|
||||
expect(injector('text')).toEqual('abc');
|
||||
});
|
||||
|
||||
it("should call function", function(){
|
||||
|
|
@ -34,14 +28,14 @@ describe('injector', function(){
|
|||
args = [this, a, b, c, d];
|
||||
}
|
||||
fn.$inject = ['a', 'b'];
|
||||
inject(fn, {name:"this"}, 3, 4);
|
||||
injector.invoke({name:"this"}, fn, [3, 4]);
|
||||
expect(args).toEqual([{name:'this'}, 1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
it('should inject providers', function(){
|
||||
providers('a', function(){return this.mi = 'Mi';});
|
||||
providers('b', function(mi){return this.name = mi+'sko';}, {$inject:['a']});
|
||||
expect(inject('b')).toEqual('Misko');
|
||||
expect(injector('b')).toEqual('Misko');
|
||||
expect(scope).toEqual({mi:'Mi', name:'Misko'});
|
||||
});
|
||||
|
||||
|
|
@ -65,24 +59,24 @@ describe('injector', function(){
|
|||
providers('s5', function(){ log.push('s5'); });
|
||||
providers('s6', function(){ log.push('s6'); });
|
||||
|
||||
inject('s1');
|
||||
injector('s1');
|
||||
|
||||
expect(log).toEqual(['s6', 's3', 's5', 's4', 's2', 's1']);
|
||||
expect(log).toEqual(['s6', 's5', 's3', 's4', 's2', 's1']);
|
||||
});
|
||||
|
||||
|
||||
it('should provide usefull message if no provider', function(){
|
||||
assertThrows("Unknown provider for 'idontexist'.", function(){
|
||||
inject('idontexist');
|
||||
});
|
||||
expect(function(){
|
||||
injector('idontexist');
|
||||
}).toThrow("Unknown provider for 'idontexist'.");
|
||||
});
|
||||
|
||||
it('should autostart eager services', function(){
|
||||
var log = '';
|
||||
providers('eager', function(){log += 'eager;'; return 'foo';}, {$eager: true});
|
||||
inject();
|
||||
injector.eager();
|
||||
expect(log).toEqual('eager;');
|
||||
expect(inject('eager')).toBe('foo');
|
||||
expect(injector('eager')).toBe('foo');
|
||||
});
|
||||
|
||||
describe('annotation', function(){
|
||||
|
|
@ -105,9 +99,10 @@ describe('injector', function(){
|
|||
multi-line comment
|
||||
function (a, b){}
|
||||
*/
|
||||
/* {some type} */ c){ extraParans();}
|
||||
expect(injectionArgs($f_n0)).toEqual(['$a', 'b']);
|
||||
expect($f_n0.$inject).toEqual(['$a', 'b']);
|
||||
_c,
|
||||
/* {some type} */ d){ extraParans();}
|
||||
expect(injectionArgs($f_n0)).toEqual(['$a', 'b_', '_c', 'd']);
|
||||
expect($f_n0.$inject).toEqual(['$a', 'b_', '_c', 'd']);
|
||||
});
|
||||
|
||||
it('should handle no arg functions', function(){
|
||||
|
|
@ -118,8 +113,8 @@ describe('injector', function(){
|
|||
|
||||
it('should handle args with both $ and _', function(){
|
||||
function $f_n0($a_){}
|
||||
expect(injectionArgs($f_n0)).toEqual(['$a']);
|
||||
expect($f_n0.$inject).toEqual(['$a']);
|
||||
expect(injectionArgs($f_n0)).toEqual(['$a_']);
|
||||
expect($f_n0.$inject).toEqual(['$a_']);
|
||||
});
|
||||
|
||||
it('should throw on non function arg', function(){
|
||||
|
|
@ -128,11 +123,17 @@ describe('injector', function(){
|
|||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should throw on injectable after non-injectable arg', function(){
|
||||
expect(function(){
|
||||
injectionArgs(function($a, b_, nonInject, d_){});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
describe('inject', function(){
|
||||
it('should inject names', function(){
|
||||
expect(angular.annotate('a', {}).$inject).toEqual(['a']);
|
||||
expect(angular.annotate('a', 'b', {}).$inject).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('should inject array', function(){
|
||||
expect(angular.annotate(['a'], {}).$inject).toEqual(['a']);
|
||||
expect(angular.annotate(['a', 'b'], {}).$inject).toEqual(['a', 'b']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ function dealoc(obj) {
|
|||
}
|
||||
|
||||
extend(angular, {
|
||||
'annotate': annotate,
|
||||
'element': jqLite,
|
||||
'compile': compile,
|
||||
'scope': createScope,
|
||||
|
|
@ -168,6 +169,7 @@ extend(angular, {
|
|||
'toJson': toJson,
|
||||
'fromJson': fromJson,
|
||||
'identity':identity,
|
||||
'injector': createInjector,
|
||||
'isUndefined': isUndefined,
|
||||
'isDefined': isDefined,
|
||||
'isString': isString,
|
||||
|
|
|
|||
Loading…
Reference in a new issue