aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMisko Hevery2011-10-31 15:33:52 -0700
committerMisko Hevery2011-11-14 16:39:32 -0800
commitc925f8a6578e05c8136c206f2fd98eeaaf1c0f16 (patch)
treee0211ce36aabe753fd633874ea5338612ab9f9fb
parent4c10d33eb4340d6df30e298d63f4d37c25aab65f (diff)
downloadangular.js-c925f8a6578e05c8136c206f2fd98eeaaf1c0f16.tar.bz2
new(injector): new injector v2.0
- not connected to keep the CL small
-rw-r--r--src/Angular.js2
-rw-r--r--src/Injector.js139
-rw-r--r--test/AngularSpec.js2
-rw-r--r--test/InjectorSpec.js281
-rw-r--r--test/testabilityPatch.js2
-rw-r--r--test/widgetsSpec.js2
6 files changed, 412 insertions, 16 deletions
diff --git a/src/Angular.js b/src/Angular.js
index 29199f9b..bbd43d3b 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -948,7 +948,7 @@ function angularInit(config, document){
if (autobind) {
var element = isString(autobind) ? document.getElementById(autobind) : document,
- injector = createInjector(),
+ injector = createInjector(angularService),
scope = injector('$rootScope');
injector('$compile')(element)(scope);
diff --git a/src/Injector.js b/src/Injector.js
index 624246af..fe0cc5e9 100644
--- a/src/Injector.js
+++ b/src/Injector.js
@@ -154,3 +154,142 @@ function inferInjectionArgs(fn) {
}
return fn.$inject;
}
+
+///////////////////////////////////////
+function createInjector2(modulesToLoad, moduleRegistry) {
+ var cache = {},
+ $injector = internalInjector(cache),
+ providerSuffix = 'Provider',
+ providerSuffixLength = providerSuffix.length;
+
+ function $provide(name) {
+ var provider = cache['#' + name + providerSuffix];
+ if (provider) {
+ return provider;
+ } else {
+ throw Error("No provider for: " + name);
+ }
+ }
+
+ $provide.service = function(name, provider) {
+ if (isFunction(provider)){
+ provider = $injector.instantiate(provider);
+ }
+ if (!provider.$get) {
+ throw Error('Providers must define $get factory method.');
+ }
+ cache['#' + name + providerSuffix] = provider;
+ };
+ $provide.factory = function(name, factoryFn) { $provide.service(name, { $get:factoryFn }); };
+ $provide.value = function(name, value) { $provide.factory(name, valueFn(value)); };
+
+ $provide.value('$injector', $injector);
+ $provide.value('$provide', $provide);
+
+ function internalInjector(cache) {
+ var path = [];
+
+ function injector(value) {
+ switch(typeof value) {
+ case 'function':
+ return invoke(null, value);
+ case 'string':
+ var instanceKey = '#' + value;
+ if (cache[instanceKey]) {
+ return cache[instanceKey];
+ }
+ try {
+ path.unshift(value);
+ var providerKey = instanceKey + providerSuffix,
+ provider = cache[providerKey];
+ if (provider) {
+ return cache[instanceKey] = invoke(provider, provider.$get);
+ } else {
+ throw Error("Unknown provider for '" + path.join("' <- '") + "'.");
+ }
+ } finally {
+ path.shift();
+ }
+ case 'object':
+ if (value instanceof Array) {
+ return invoke(null, value);
+ }
+ default:
+ throw Error('Injector expects name or function.');
+ }
+ }
+
+ function invoke(self, fn){
+ var args = [],
+ $inject,
+ length;
+
+ switch(typeof fn){
+ case 'function':
+ $inject = inferInjectionArgs(fn);
+ length = $inject.length;
+ break;
+ case 'object':
+ if (typeof fn.length == 'number') {
+ $inject = fn;
+ length = $inject.length;
+ fn = $inject[--length];
+ }
+ default:
+ assertArgFn(fn, 'fn');
+ };
+
+ while(length--) {
+ args.unshift(injector($inject[length], path));
+ }
+
+ switch (self ? -1 : args.length) {
+ case 0: return fn();
+ case 1: return fn(args[0]);
+ case 2: return fn(args[0], args[1]);
+ case 3: return fn(args[0], args[1], args[2]);
+ case 4: return fn(args[0], args[1], args[2], args[3]);
+ case 5: return fn(args[0], args[1], args[2], args[3], args[4]);
+ case 6: return fn(args[0], args[1], args[2], args[3], args[4], args[5]);
+ case 7: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+ case 8: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
+ case 9: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
+ case 10: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);
+ default: return fn.apply(self, args);
+ }
+ };
+
+ injector.invoke = invoke;
+ injector.instantiate = function(Type, locals){
+ var Constructor = function(){},
+ instance;
+ Constructor.prototype = Type.prototype;
+ instance = new Constructor();
+ return invoke(instance, Type, locals) || instance;
+ };
+ return injector;
+ }
+
+
+ forEach(modulesToLoad, function(module){
+ if (isString(module)) {
+ module = moduleRegistry[module];
+ }
+ if (isFunction(module) || isArray(module)) {
+ $injector(module);
+ } else {
+ assertArgFn(module, 'module');
+ }
+ });
+
+ // instantiate $eager providers
+ // for perf we can't do forEach
+ for(var name in cache) {
+ var index = name.indexOf(providerSuffix);
+ if (index == name.length - providerSuffixLength && cache[name].$eager) {
+ $injector(name.substring(1, index));
+ }
+ }
+
+ return $injector;
+}
diff --git a/test/AngularSpec.js b/test/AngularSpec.js
index 7e6f35fa..a957fcf7 100644
--- a/test/AngularSpec.js
+++ b/test/AngularSpec.js
@@ -416,7 +416,7 @@ describe('angular', function() {
it('should eagerly instantiate a service if $eager is true', function() {
var log = [];
angular.service('svc1', function() { log.push('svc1'); }, {$eager: true});
- createInjector();
+ createInjector(angularService);
expect(log).toEqual(['svc1']);
});
});
diff --git a/test/InjectorSpec.js b/test/InjectorSpec.js
index 6ac17876..8c36677d 100644
--- a/test/InjectorSpec.js
+++ b/test/InjectorSpec.js
@@ -21,7 +21,7 @@ describe('injector', function() {
it('should inject providers', function() {
providers('a', function() {return 'Mi';});
- providers('b', function(mi){return mi+'sko';}, {$inject:['a']});
+ providers('b', function(mi) {return mi+'sko';}, {$inject:['a']});
expect(injector('b')).toEqual('Misko');
});
@@ -57,7 +57,7 @@ describe('injector', function() {
}).toThrow("Unknown provider for 'idontexist'.");
});
- it('should proved path to the missing provider', function(){
+ it('should proved path to the missing provider', function() {
expect(function() {
injector('idontexist', ['a', 'b']);
}).toThrow("Unknown provider for 'idontexist' <- 'a' <- 'b'.");
@@ -71,10 +71,10 @@ describe('injector', function() {
expect(injector('eager')).toBe('foo');
});
- describe('invoke', function(){
+ describe('invoke', function() {
var args;
- beforeEach(function(){
+ beforeEach(function() {
args = null;
providers('a', function() {return 1;});
providers('b', function() {return 2;});
@@ -94,7 +94,7 @@ describe('injector', function() {
});
- it('should treat array as annotations', function(){
+ it('should treat array as annotations', function() {
injector.invoke({name:"this"}, ['a', 'b', fn], [3, 4]);
expect(args).toEqual([{name:'this'}, 1, 2, 3, 4]);
});
@@ -105,11 +105,11 @@ describe('injector', function() {
});
- it('should fail with errors if not function or array', function(){
- expect(function(){
+ it('should fail with errors if not function or array', function() {
+ expect(function() {
injector.invoke({}, {});
}).toThrow("Argument 'fn' is not a function, got Object");
- expect(function(){
+ expect(function() {
injector.invoke({}, ['a', 123]);
}).toThrow("Argument 'fn' is not a function, got number");
});
@@ -133,10 +133,10 @@ describe('injector', function() {
$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){}
+ function (a, b) {}
*/
_c,
- /* {some type} */ d){ extraParans();}
+ /* {some type} */ d) { extraParans();}
expect(inferInjectionArgs($f_n0)).toEqual(['$a', 'b_', '_c', 'd']);
expect($f_n0.$inject).toEqual(['$a', 'b_', '_c', 'd']);
});
@@ -148,7 +148,7 @@ describe('injector', function() {
});
it('should handle args with both $ and _', function() {
- function $f_n0($a_){}
+ function $f_n0($a_) {}
expect(inferInjectionArgs($f_n0)).toEqual(['$a_']);
expect($f_n0.$inject).toEqual(['$a_']);
});
@@ -162,7 +162,7 @@ describe('injector', function() {
it('should infer injection on services', function() {
var $injector = createInjector({
a: function() { return 'a';},
- b: function(a){ return a + 'b';}
+ b: function(a) { return a + 'b';}
});
expect($injector('b')).toEqual('ab');
});
@@ -180,3 +180,260 @@ describe('injector', function() {
});
});
});
+
+describe('injector2', function() {
+
+ it('should have $injector', function() {
+ var $injector = createInjector2();
+ expect($injector('$injector')).toBe($injector);
+ });
+
+ it('should define module', function() {
+ var log = '';
+ var injector = createInjector2([function($provide) {
+ $provide.value('value', 'value;');
+ $provide.factory('fn', valueFn('function;'));
+ $provide.service('service', function() {
+ this.$get = valueFn('service;');
+ });
+ }, function(valueProvider, fnProvider, serviceProvider) {
+ log += valueProvider.$get() + fnProvider.$get() + serviceProvider.$get();
+ }])(function(value, fn, service) {
+ log += '->' + value + fn + service;
+ });
+ expect(log).toEqual('value;function;service;->value;function;service;');
+ });
+
+
+ describe('module', function() {
+ it('should provide $injector and $provide even when no module is requested', function() {
+ var $provide,
+ $injector = createInjector2([
+ angular.extend(function(p) { $provide = p; }, {$inject: ['$provide']})
+ ]);
+ expect($injector('$injector')).toBe($injector);
+ expect($injector('$provide')).toBe($provide);
+ });
+
+
+ it('should load multiple function modules and infer inject them', function() {
+ var a = 'junk';
+ var $injector = createInjector2([
+ function() {
+ a = 'A'; // reset to prove we ran
+ },
+ function($provide) {
+ $provide.value('a', a);
+ },
+ angular.extend(function(p, serviceA) {
+ p.value('b', serviceA.$get() + 'B' );
+ }, {$inject:['$provide', 'aProvider']}),
+ ['$provide', 'bProvider', function(p, serviceB) {
+ p.value('c', serviceB.$get() + 'C');
+ }]
+ ]);
+ expect($injector('a')).toEqual('A');
+ expect($injector('b')).toEqual('AB');
+ expect($injector('c')).toEqual('ABC');
+ });
+
+
+ it('should run symbolic modules', function() {
+ var $injector = createInjector2(['myModule'], {
+ myModule: ['$provide', function(provide) {
+ provide.value('a', 'abc');
+ }]
+ });
+ expect($injector('a')).toEqual('abc');
+ });
+
+
+ describe('$provide', function() {
+ describe('value', function(){
+ it('should configure $provide values', function() {
+ expect(createInjector2([function($provide) {
+ $provide.value('value', 'abc');
+ }])('value')).toEqual('abc');
+ });
+ });
+
+
+ describe('factory', function(){
+ it('should configure $provide factory function', function() {
+ expect(createInjector2([function($provide) {
+ $provide.factory('value', valueFn('abc'));
+ }])('value')).toEqual('abc');
+ });
+ });
+
+
+ describe('service', function(){
+ it('should configure $provide service object', function() {
+ expect(createInjector2([function($provide) {
+ $provide.service('value', {
+ $get: valueFn('abc')
+ });
+ }])('value')).toEqual('abc');
+ });
+
+
+ it('should configure $provide service type', function() {
+ function Type() {};
+ Type.prototype.$get = function() {
+ expect(this instanceof Type).toBe(true);
+ return 'abc';
+ };
+ expect(createInjector2([function($provide) {
+ $provide.service('value', Type);
+ }])('value')).toEqual('abc');
+ });
+ });
+ });
+
+
+ describe('error handling', function() {
+ it('should handle wrong argument type', function() {
+ expect(function() {
+ createInjector2([
+ {}
+ ], {});
+ }).toThrow("Argument 'module' is not a function, got Object");
+ });
+
+
+ it('should handle exceptions', function() {
+ expect(function() {
+ createInjector2([function() {
+ throw 'MyError';
+ }], {});
+ }).toThrow('MyError');
+ });
+
+
+ it('should handle no module alias', function() {
+ expect(function() {
+ createInjector2([function(dontExist) {
+ }], {});
+ }).toThrow("Unknown provider for 'dontExist'.");
+ });
+ });
+ });
+
+
+ describe('retrieval', function() {
+ var instance,
+ $injector,
+ $provide;
+
+ beforeEach(function() {
+ $injector = createInjector2([ ['$provide', function(provide) {
+ ($provide = provide).value('instance', instance = {name:'angular'});
+ }]]);
+ });
+
+
+ it('should retrieve by name and cache instance', function() {
+ expect(instance).toEqual({name: 'angular'});
+ expect($injector('instance')).toBe(instance);
+ expect($injector('instance')).toBe(instance);
+ });
+
+
+ it('should call functions and infer arguments', function() {
+ expect($injector(function(instance) { return instance; })).toBe(instance);
+ expect($injector(function(instance) { return instance; })).toBe(instance);
+ });
+ });
+
+
+ describe('method invoking', function() {
+ var $injector;
+
+ beforeEach(function() {
+ $injector = createInjector2([ function($provide) {
+ $provide.value('book', 'moby');
+ $provide.value('author', 'melville');
+ }]);
+ });
+
+
+ it('should invoke method', function() {
+ expect($injector(function(book, author) { return author + ':' + book;})).toEqual('melville:moby');
+ expect($injector.invoke($injector, function(book, author) {
+ expect(this).toEqual($injector);
+ return author + ':' + book;})).toEqual('melville:moby');
+ });
+
+
+ it('should invoke method which is annotated', function() {
+ expect($injector(extend(function(b, a) { return a + ':' + b}, {$inject:['book', 'author']}))).
+ toEqual('melville:moby');
+ expect($injector.invoke($injector, extend(function(b, a) {
+ expect(this).toEqual($injector);
+ return a + ':' + b;
+ }, {$inject:['book', 'author']}))).toEqual('melville:moby');
+ });
+
+
+ it('should invoke method which is an array of annotation', function() {
+ expect($injector(function(book, author) { return author + ':' + book;})).toEqual('melville:moby');
+ expect($injector.invoke($injector, function(book, author) {
+ expect(this).toEqual($injector);
+ return author + ':' + book;
+ })).toEqual('melville:moby');
+ });
+
+
+ it('should throw usefull error on wrong argument type]', function() {
+ expect(function(){
+ $injector.invoke(null, {});
+ }).toThrow("Argument 'fn' is not a function, got Object");
+ });
+ });
+
+ describe('service instantiation', function() {
+ var $injector;
+
+ beforeEach(function() {
+ $injector = createInjector2([ function($provide) {
+ $provide.value('book', 'moby');
+ $provide.value('author', 'melville');
+ }]);
+ });
+
+
+ function Type(book, author) {
+ this.book = book;
+ this.author = author;
+ }
+ Type.prototype.title = function() {
+ return this.author + ': ' + this.book;
+ };
+
+
+ it('should instantiate object and preserve constructor property and be instanceof', function() {
+ var t = $injector.instantiate(Type);
+ expect(t.book).toEqual('moby');
+ expect(t.author).toEqual('melville');
+ expect(t.title()).toEqual('melville: moby');
+ expect(t instanceof Type).toBe(true);
+ });
+
+
+ it('should allow constructor to return different object', function() {
+ var t = $injector.instantiate(function() { return 'ABC'; });
+ expect(t).toBe('ABC');
+ });
+
+
+ it('should handle constructor exception', function() {
+ expect(function() {
+ $injector.instantiate(function() { throw 'MyError'; });
+ }).toThrow('MyError');
+ });
+ });
+
+ describe('injector chaining', function() {
+
+ });
+});
diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js
index b5949623..3e68fbed 100644
--- a/test/testabilityPatch.js
+++ b/test/testabilityPatch.js
@@ -111,7 +111,7 @@ function inject(){
fn.call(spec, spec.$service);
} else {
if (!spec.$injector) {
- spec.$injector = angular.injector(spec.$service);
+ spec.$injector = angular.injector(spec.$service || angular.service);
}
spec.$injector.invoke(spec, fn);
}
diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js
index d2204540..b7ef16a4 100644
--- a/test/widgetsSpec.js
+++ b/test/widgetsSpec.js
@@ -487,7 +487,7 @@ describe("widget", function() {
}));
it('should be possible to nest ng:view in ng:include', inject(function() {
- var injector = createInjector();
+ var injector = createInjector(angularService);
var myApp = injector('$rootScope');
var $browser = myApp.$service('$browser');
$browser.xhr.expectGET('includePartial.html').respond('view: <ng:view></ng:view>');