Skip to content

Commit d9abfe8

Browse files
committed
Introduced injector and $new to scope, and injection into link methods and controllers
- added angular.injector(scope, services, instanceCache) which returns inject - inject method can return, instance, or call function which have $inject property - initialize services with $creation=[eager|eager-publish] this means that only some of the services are now globally accessible - upgraded $become on scope to use injector hence respect the $inject property for injection - $become should not be run multiple times and will most likely be removed in future version - added $new on scope to create a child scope - $inject is respected on constructor function - simplified scopes so that they no longer have separate __proto__ for parent, api, behavior and instance this should speed up execution since scope will now create one __proto__ chain per scope (not three). BACKWARD COMPATIBILITY WARNING: - services now need to have $inject instead of inject property for proper injection this breaks backward compatibility - not all services are now published into root scope (only: $location, $cookie, $window) - if you have widget/directive which uses services on scope (such as this.$xhr), you will now have to inject that service in (as it is not published on the root scope anymore)
1 parent fbfd160 commit d9abfe8

17 files changed

Lines changed: 347 additions & 213 deletions

Rakefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ task :compile_scenario do
2929
src/jqLite.js \
3030
src/JSON.js \
3131
src/Scope.js \
32+
src/Injector.js \
3233
src/Parser.js \
3334
src/Resource.js \
3435
src/Browser.js \

src/Angular.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,10 @@ function escapeAttr(html) {
330330
'"');
331331
}
332332

333+
function concat(array1, array2, index) {
334+
return array1.concat(slice.call(array2, index, array2.length));
335+
}
336+
333337
function bind(self, fn) {
334338
var curryArgs = arguments.length > 2 ? slice.call(arguments, 2, arguments.length) : [];
335339
if (typeof fn == $function) {
@@ -403,7 +407,7 @@ function angularInit(config){
403407
// TODO default to the source of angular.js
404408
var scope = compile(window.document, _null, {'$config':config});
405409
if (config.css)
406-
scope.$browser.addCss(config.base_url + config.css);
410+
scope.$inject('$browser').addCss(config.base_url + config.css);
407411
scope.$init();
408412
}
409413
}

src/AngularPublic.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ extend(angular, {
2121
'extend': extend,
2222
'equals': equals,
2323
'foreach': foreach,
24+
'injector': createInjector,
2425
'noop':noop,
2526
'bind':bind,
2627
'toJson': toJson,

src/Compiler.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Template.prototype = {
3434
foreach(this.inits, function(fn) {
3535
queue.push(function() {
3636
childScope.$tryEval(function(){
37-
return fn.call(childScope, element);
37+
return childScope.$inject(fn, childScope, element);
3838
}, element);
3939
});
4040
});
@@ -96,8 +96,7 @@ Compiler.prototype = {
9696
return function(element, parentScope){
9797
element = jqLite(element);
9898
var scope = parentScope && parentScope.$eval ?
99-
parentScope :
100-
createScope(parentScope || {}, angularService);
99+
parentScope : createScope(parentScope);
101100
return extend(scope, {
102101
$element:element,
103102
$init: function() {
@@ -124,7 +123,7 @@ Compiler.prototype = {
124123
text:function(text) {return jqLite(document.createTextNode(text));},
125124
descend: function(value){ if(isDefined(value)) descend = value; return descend;},
126125
directives: function(value){ if(isDefined(value)) directives = value; return directives;},
127-
scope: function(value){ if(isDefined(value)) template.newScope = template.newScope || value ; return template.newScope;}
126+
scope: function(value){ if(isDefined(value)) template.newScope = template.newScope || value; return template.newScope;}
128127
};
129128
try {
130129
priority = element.attr('ng:eval-order') || priority || 0;

src/Injector.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Create an inject method
3+
* @param providerScope provider's "this"
4+
* @param providers a function(name) which returns provider function
5+
* @param cache place where instances are saved for reuse
6+
* @returns {Function}
7+
*/
8+
function createInjector(providerScope, providers, cache) {
9+
providers = providers || angularService;
10+
cache = cache || {};
11+
providerScope = providerScope || {};
12+
/**
13+
* injection function
14+
* @param value: string, array, object or function.
15+
* @param scope: optional function "this"
16+
* @param args: optional arguments to pass to function after injection
17+
* parameters
18+
* @returns depends on value:
19+
* string: return an instance for the injection key.
20+
* array of keys: returns an array of instances.
21+
* function: look at $inject property of function to determine instances
22+
* and then call the function with instances and scope. Any
23+
* additional arguments are passed on to function.
24+
* object: initialize eager providers and publish them the ones with publish here.
25+
* none: same as object but use providerScope as place to publish.
26+
*/
27+
return function inject(value, scope, args){
28+
var returnValue, provider, creation;
29+
if (isString(value)) {
30+
if (!cache.hasOwnProperty(value)) {
31+
provider = providers[value];
32+
if (!provider) throw "Unknown provider for '"+value+"'.";
33+
cache[value] = inject(provider, providerScope);
34+
}
35+
returnValue = cache[value];
36+
} else if (isArray(value)) {
37+
returnValue = [];
38+
foreach(value, function(name) {
39+
returnValue.push(inject(name));
40+
});
41+
} else if (isFunction(value)) {
42+
returnValue = inject(value.$inject || []);
43+
returnValue = value.apply(scope, concat(returnValue, arguments, 2));
44+
} else if (isObject(value)) {
45+
foreach(providers, function(provider, name){
46+
creation = provider.$creation;
47+
if (creation == 'eager') {
48+
inject(name);
49+
}
50+
if (creation == 'eager-published') {
51+
setter(value, name, inject(name));
52+
}
53+
});
54+
} else {
55+
returnValue = inject(providerScope);
56+
}
57+
return returnValue;
58+
};
59+
}

src/Scope.js

Lines changed: 19 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -108,20 +108,14 @@ function errorHandlerFor(element, error) {
108108
elementError(element, NG_EXCEPTION, isDefined(error) ? toJson(error) : error);
109109
}
110110

111-
function createScope(parent, services, existing) {
111+
function createScope(parent, providers, instanceCache) {
112112
function Parent(){}
113-
function API(){}
114-
function Behavior(){}
115-
116113
parent = Parent.prototype = (parent || {});
114+
var instance = new Parent();
117115
var evalLists = {sorted:[]};
118116
var postList = [], postHash = {}, postId = 0;
119-
var servicesCache = extend({}, existing);
120-
var api = API.prototype = new Parent();
121-
var behavior = Behavior.prototype = new API();
122-
var instance = new Behavior();
123117

124-
extend(api, {
118+
extend(instance, {
125119
'this': instance,
126120
$id: (scopeId++),
127121
$parent: parent,
@@ -227,46 +221,29 @@ function createScope(parent, services, existing) {
227221
},
228222

229223
$become: function(Class) {
230-
// remove existing
231-
foreach(behavior, function(value, key){ delete behavior[key]; });
232-
foreach((Class || noop).prototype, function(fn, name){
233-
behavior[name] = bind(instance, fn);
234-
});
235-
(Class || noop).call(instance);
236-
237-
//TODO: backwards compatibility hack, remove when Feedback's init methods are removed
238-
if (behavior.hasOwnProperty('init')) {
239-
behavior.init();
224+
if (isFunction(Class)) {
225+
instance.constructor = Class;
226+
foreach(Class.prototype, function(fn, name){
227+
instance[name] = bind(instance, fn);
228+
});
229+
instance.$inject.apply(instance, concat([Class, instance], arguments, 1));
240230
}
231+
},
232+
233+
$new: function(Class) {
234+
var child = createScope(instance);
235+
child.$become.apply(instance, concat([Class], arguments, 1));
236+
instance.$onEval(child.$eval);
237+
return child;
241238
}
242239

243240
});
244241

245242
if (!parent.$root) {
246-
api.$root = instance;
247-
api.$parent = instance;
248-
}
249-
250-
function inject(name){
251-
var service = servicesCache[name], factory, args = [];
252-
if (isUndefined(service)) {
253-
factory = services[name];
254-
if (!isFunction(factory))
255-
throw "Don't know how to inject '" + name + "'.";
256-
foreach(factory.inject, function(dependency){
257-
args.push(inject(dependency));
258-
});
259-
servicesCache[name] = service = factory.apply(instance, args);
260-
}
261-
return service;
243+
instance.$root = instance;
244+
instance.$parent = instance;
245+
(instance.$inject = createInjector(instance, providers, instanceCache))();
262246
}
263247

264-
foreach(services, function(_, name){
265-
var service = inject(name);
266-
if (service) {
267-
setter(instance, name, service);
268-
}
269-
});
270-
271248
return instance;
272249
}

src/angular-bootstrap.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
addScript("/JSON.js");
4242
addScript("/Compiler.js");
4343
addScript("/Scope.js");
44+
addScript("/Injector.js");
4445
addScript("/jqLite.js");
4546
addScript("/Parser.js");
4647
addScript("/Resource.js");

0 commit comments

Comments
 (0)