refactor(ElementInjector): support components

- Allow to access containing component directive instance from the shadow DOM.
- Allow to access app services of the app level injector of the component
  when the component is instantiated.
This commit is contained in:
Tobias Bosch 2014-11-12 11:17:46 -08:00
parent c68e78075a
commit 7308a3acc7
8 changed files with 207 additions and 95 deletions

View File

@ -10,8 +10,8 @@ export function run () {
var bindings = [A, B, C]; var bindings = [A, B, C];
var proto = new ProtoElementInjector(null, 0, bindings); var proto = new ProtoElementInjector(null, 0, bindings);
for (var i = 0; i < ITERATIONS; ++i) { for (var i = 0; i < ITERATIONS; ++i) {
var ei = proto.instantiate(null,null); var ei = proto.instantiate(null,null,null);
ei.instantiateDirectives(appInjector); ei.instantiateDirectives(appInjector, null);
} }
} }

View File

@ -6,7 +6,7 @@ var count = 0;
export function run () { export function run () {
var appInjector = new Injector([]); var appInjector = new Injector([]);
var bindings = [ var bindings = [
new Binding(Key.get(A), () => new A(), [], false), new Binding(Key.get(A), () => new A(), [], false),
new Binding(Key.get(B), () => new B(), [], false), new Binding(Key.get(B), () => new B(), [], false),
@ -18,8 +18,8 @@ export function run () {
var proto = new ProtoElementInjector(null, 0, bindings); var proto = new ProtoElementInjector(null, 0, bindings);
for (var i = 0; i < ITERATIONS; ++i) { for (var i = 0; i < ITERATIONS; ++i) {
var ei = proto.instantiate(null,null); var ei = proto.instantiate(null,null,null);
ei.instantiateDirectives(appInjector); ei.instantiateDirectives(appInjector, null);
} }
} }

View File

@ -9,11 +9,11 @@ export function run () {
var bindings = [A, B, C]; var bindings = [A, B, C];
var proto = new ProtoElementInjector(null, 0, bindings); var proto = new ProtoElementInjector(null, 0, bindings);
var ei = proto.instantiate(null,null); var ei = proto.instantiate(null,null,null);
for (var i = 0; i < ITERATIONS; ++i) { for (var i = 0; i < ITERATIONS; ++i) {
ei.clearDirectives(); ei.clearDirectives();
ei.instantiateDirectives(appInjector); ei.instantiateDirectives(appInjector, null);
} }
} }

View File

@ -1,4 +1,4 @@
import {FIELD, isPresent, isBlank, Type, int} from 'facade/lang'; import {FIELD, isPresent, isBlank, Type, int, BaseException} from 'facade/lang';
import {Math} from 'facade/math'; import {Math} from 'facade/math';
import {List, ListWrapper} from 'facade/collection'; import {List, ListWrapper} from 'facade/collection';
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'di/di'; import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'di/di';
@ -106,6 +106,7 @@ export class ProtoElementInjector {
@FIELD('_binding7:Binding') @FIELD('_binding7:Binding')
@FIELD('_binding8:Binding') @FIELD('_binding8:Binding')
@FIELD('_binding9:Binding') @FIELD('_binding9:Binding')
@FIELD('_binding0IsComponent:int')
@FIELD('_key0:int') @FIELD('_key0:int')
@FIELD('_key1:int') @FIELD('_key1:int')
@FIELD('_key2:int') @FIELD('_key2:int')
@ -118,10 +119,11 @@ export class ProtoElementInjector {
@FIELD('_key9:int') @FIELD('_key9:int')
@FIELD('final parent:ProtoElementInjector') @FIELD('final parent:ProtoElementInjector')
@FIELD('final index:int') @FIELD('final index:int')
constructor(parent:ProtoElementInjector, index:int, bindings:List) { constructor(parent:ProtoElementInjector, index:int, bindings:List, firstBindingIsComponent:boolean = false) {
this.parent = parent; this.parent = parent;
this.index = index; this.index = index;
this._binding0IsComponent = firstBindingIsComponent;
this._binding0 = null; this._keyId0 = null; this._binding0 = null; this._keyId0 = null;
this._binding1 = null; this._keyId1 = null; this._binding1 = null; this._keyId1 = null;
this._binding2 = null; this._keyId2 = null; this._binding2 = null; this._keyId2 = null;
@ -150,8 +152,8 @@ export class ProtoElementInjector {
} }
} }
instantiate(parent:ElementInjector, view):ElementInjector { instantiate(parent:ElementInjector, host:ElementInjector, view):ElementInjector {
return new ElementInjector(this, parent, view); return new ElementInjector(this, parent, host, view);
} }
_createBinding(bindingOrType) { _createBinding(bindingOrType) {
@ -212,7 +214,9 @@ export class ElementInjector extends TreeNode {
*/ */
@FIELD('_proto:ProtoElementInjector') @FIELD('_proto:ProtoElementInjector')
@FIELD('_appInjector:Injector') @FIELD('_lightDomAppInjector:Injector')
@FIELD('_shadowDomAppInjector:Injector')
@FIELD('_host:ElementInjector')
@FIELD('_obj0:Object') @FIELD('_obj0:Object')
@FIELD('_obj1:Object') @FIELD('_obj1:Object')
@FIELD('_obj2:Object') @FIELD('_obj2:Object')
@ -224,13 +228,24 @@ export class ElementInjector extends TreeNode {
@FIELD('_obj8:Object') @FIELD('_obj8:Object')
@FIELD('_obj9:Object') @FIELD('_obj9:Object')
@FIELD('_view:View') @FIELD('_view:View')
constructor(proto:ProtoElementInjector, parent:ElementInjector, view) { constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector, view) {
super(parent); super(parent);
if (isPresent(parent) && isPresent(host)) {
throw new BaseException('Only either parent or host is allowed');
}
this._host = null; // needed to satisfy Dart
if (isPresent(parent)) {
this._host = parent._host;
} else {
this._host = host;
}
this._proto = proto; this._proto = proto;
this._view = view; this._view = view;
//we cannot call clearDirectives because fields won't be detected //we cannot call clearDirectives because fields won't be detected
this._appInjector = null; this._lightDomAppInjector = null;
this._shadowDomAppInjector = null;
this._obj0 = null; this._obj0 = null;
this._obj1 = null; this._obj1 = null;
this._obj2 = null; this._obj2 = null;
@ -245,7 +260,9 @@ export class ElementInjector extends TreeNode {
} }
clearDirectives() { clearDirectives() {
this._appInjector = null; this._lightDomAppInjector = null;
this._shadowDomAppInjector = null;
this._obj0 = null; this._obj0 = null;
this._obj1 = null; this._obj1 = null;
this._obj2 = null; this._obj2 = null;
@ -259,24 +276,42 @@ export class ElementInjector extends TreeNode {
this._constructionCounter = 0; this._constructionCounter = 0;
} }
instantiateDirectives(appInjector:Injector) { instantiateDirectives(lightDomAppInjector:Injector, shadowDomAppInjector:Injector) {
this._appInjector = appInjector;
var p = this._proto; var p = this._proto;
if (this._proto._binding0IsComponent && isBlank(shadowDomAppInjector)) {
throw new BaseException('A shadowDomAppInjector is required as this ElementInjector contains a component');
} else if (!this._proto._binding0IsComponent && isPresent(shadowDomAppInjector)) {
throw new BaseException('No shadowDomAppInjector allowed as there is not component stored in this ElementInjector');
}
this._lightDomAppInjector = lightDomAppInjector;
this._shadowDomAppInjector = shadowDomAppInjector;
if (isPresent(p._keyId0)) this._getDirectiveByKeyId(p._keyId0); if (isPresent(p._keyId0)) this._getDirectiveByKeyId(p._keyId0);
if (isPresent(p._keyId1)) this._getDirectiveByKeyId(p._keyId1); if (isPresent(p._keyId1)) this._getDirectiveByKeyId(p._keyId1);
if (isPresent(p._keyId2)) this._getDirectiveByKeyId(p._keyId2);; if (isPresent(p._keyId2)) this._getDirectiveByKeyId(p._keyId2);
if (isPresent(p._keyId3)) this._getDirectiveByKeyId(p._keyId3);; if (isPresent(p._keyId3)) this._getDirectiveByKeyId(p._keyId3);
if (isPresent(p._keyId4)) this._getDirectiveByKeyId(p._keyId4);; if (isPresent(p._keyId4)) this._getDirectiveByKeyId(p._keyId4);
if (isPresent(p._keyId5)) this._getDirectiveByKeyId(p._keyId5);; if (isPresent(p._keyId5)) this._getDirectiveByKeyId(p._keyId5);
if (isPresent(p._keyId6)) this._getDirectiveByKeyId(p._keyId6);; if (isPresent(p._keyId6)) this._getDirectiveByKeyId(p._keyId6);
if (isPresent(p._keyId7)) this._getDirectiveByKeyId(p._keyId7);; if (isPresent(p._keyId7)) this._getDirectiveByKeyId(p._keyId7);
if (isPresent(p._keyId8)) this._getDirectiveByKeyId(p._keyId8);; if (isPresent(p._keyId8)) this._getDirectiveByKeyId(p._keyId8);
if (isPresent(p._keyId9)) this._getDirectiveByKeyId(p._keyId9);; if (isPresent(p._keyId9)) this._getDirectiveByKeyId(p._keyId9);
} }
get(token) { get(token) {
return this._getByKey(Key.get(token), 0); return this._getByKey(Key.get(token), 0, null);
}
getComponent() {
if (this._proto._binding0IsComponent) {
return this._obj0;
} else {
throw new BaseException('There is not component stored in this ElementInjector');
}
}
_isComponentKey(key:Key) {
return this._proto._binding0IsComponent && key.id === this._proto._keyId0;
} }
_new(binding:Binding) { _new(binding:Binding) {
@ -290,16 +325,16 @@ export class ElementInjector extends TreeNode {
var d0,d1,d2,d3,d4,d5,d6,d7,d8,d9; var d0,d1,d2,d3,d4,d5,d6,d7,d8,d9;
try { try {
d0 = length > 0 ? this._getByDependency(deps[0]) : null; d0 = length > 0 ? this._getByDependency(deps[0], binding.key) : null;
d1 = length > 1 ? this._getByDependency(deps[1]) : null; d1 = length > 1 ? this._getByDependency(deps[1], binding.key) : null;
d2 = length > 2 ? this._getByDependency(deps[2]) : null; d2 = length > 2 ? this._getByDependency(deps[2], binding.key) : null;
d3 = length > 3 ? this._getByDependency(deps[3]) : null; d3 = length > 3 ? this._getByDependency(deps[3], binding.key) : null;
d4 = length > 4 ? this._getByDependency(deps[4]) : null; d4 = length > 4 ? this._getByDependency(deps[4], binding.key) : null;
d5 = length > 5 ? this._getByDependency(deps[5]) : null; d5 = length > 5 ? this._getByDependency(deps[5], binding.key) : null;
d6 = length > 6 ? this._getByDependency(deps[6]) : null; d6 = length > 6 ? this._getByDependency(deps[6], binding.key) : null;
d7 = length > 7 ? this._getByDependency(deps[7]) : null; d7 = length > 7 ? this._getByDependency(deps[7], binding.key) : null;
d8 = length > 8 ? this._getByDependency(deps[8]) : null; d8 = length > 8 ? this._getByDependency(deps[8], binding.key) : null;
d9 = length > 9 ? this._getByDependency(deps[9]) : null; d9 = length > 9 ? this._getByDependency(deps[9], binding.key) : null;
} catch(e) { } catch(e) {
if (e instanceof ProviderError) e.addKey(binding.key); if (e instanceof ProviderError) e.addKey(binding.key);
throw e; throw e;
@ -324,8 +359,8 @@ export class ElementInjector extends TreeNode {
return obj; return obj;
} }
_getByDependency(dep:DirectiveDependency) { _getByDependency(dep:DirectiveDependency, requestor:Key) {
return this._getByKey(dep.key, dep.depth); return this._getByKey(dep.key, dep.depth, requestor);
} }
/* /*
@ -340,7 +375,7 @@ export class ElementInjector extends TreeNode {
* *
* Write benchmarks before doing this optimization. * Write benchmarks before doing this optimization.
*/ */
_getByKey(key:Key, depth:int) { _getByKey(key:Key, depth:int, requestor:Key) {
var ei = this; var ei = this;
while (ei != null && depth >= 0) { while (ei != null && depth >= 0) {
var specObj = ei._getSpecialObjectByKeyId(key.id); var specObj = ei._getSpecialObjectByKeyId(key.id);
@ -352,7 +387,17 @@ export class ElementInjector extends TreeNode {
ei = ei._parent; ei = ei._parent;
depth -= 1; depth -= 1;
} }
return this._appInjector.get(key); if (isPresent(this._host) && this._host._isComponentKey(key)) {
return this._host.getComponent();
} else {
var appInjector;
if (isPresent(requestor) && this._isComponentKey(requestor)) {
appInjector = this._shadowDomAppInjector;
} else {
appInjector = this._lightDomAppInjector;
}
return appInjector.get(key);
}
} }
_getSpecialObjectByKeyId(keyId:int) { _getSpecialObjectByKeyId(keyId:int) {
@ -364,6 +409,7 @@ export class ElementInjector extends TreeNode {
_getDirectiveByKeyId(keyId:int) { _getDirectiveByKeyId(keyId:int) {
var p = this._proto; var p = this._proto;
if (p._keyId0 === keyId) {if (isBlank(this._obj0)){this._obj0 = this._new(p._binding0);} return this._obj0;} if (p._keyId0 === keyId) {if (isBlank(this._obj0)){this._obj0 = this._new(p._binding0);} return this._obj0;}
if (p._keyId1 === keyId) {if (isBlank(this._obj1)){this._obj1 = this._new(p._binding1);} return this._obj1;} if (p._keyId1 === keyId) {if (isBlank(this._obj1)){this._obj1 = this._new(p._binding1);} return this._obj1;}
if (p._keyId2 === keyId) {if (isBlank(this._obj2)){this._obj2 = this._new(p._binding2);} return this._obj2;} if (p._keyId2 === keyId) {if (isBlank(this._obj2)){this._obj2 = this._new(p._binding2);} return this._obj2;}

View File

@ -1,7 +1,8 @@
import {isPresent,} from 'facade/lang'; import {isPresent, isBlank} from 'facade/lang';
import {ListWrapper} from 'facade/collection'; import {ListWrapper} from 'facade/collection';
import {ProtoElementInjector} from '../element_injector'; import {Key} from 'di/di';
import {ProtoElementInjector, ComponentKeyMetaData} from '../element_injector';
import {CompileStep} from './compile_step'; import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
@ -23,22 +24,23 @@ import {CompileControl} from './compile_control';
*/ */
export class ProtoElementInjectorBuilder extends CompileStep { export class ProtoElementInjectorBuilder extends CompileStep {
// public so that we can overwrite it in tests // public so that we can overwrite it in tests
internalCreateProtoElementInjector(parent, index, directives) { internalCreateProtoElementInjector(parent, index, directives, firstBindingIsComponent) {
return new ProtoElementInjector(parent, index, directives); return new ProtoElementInjector(parent, index, directives, firstBindingIsComponent);
} }
process(parent:CompileElement, current:CompileElement, control:CompileControl) { process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var inheritedProtoElementInjector = null; var inheritedProtoElementInjector = null;
var parentProtoElementInjector = this._getParentProtoElementInjector(parent, current); var parentProtoElementInjector = this._getParentProtoElementInjector(parent, current);
var injectorBindings = this._collectDirectiveTypes(current); var injectorBindings = this._collectDirectiveBindings(current);
// TODO: add lightDomServices as well, // TODO: add lightDomServices as well,
// but after the directives as we rely on that order // but after the directives as we rely on that order
// in the element_binder_builder. // in the element_binder_builder.
if (injectorBindings.length > 0) { if (injectorBindings.length > 0) {
var protoView = current.inheritedProtoView; var protoView = current.inheritedProtoView;
var hasComponent = isPresent(current.componentDirective);
inheritedProtoElementInjector = this.internalCreateProtoElementInjector( inheritedProtoElementInjector = this.internalCreateProtoElementInjector(
parentProtoElementInjector, protoView.elementBinders.length, injectorBindings parentProtoElementInjector, protoView.elementBinders.length, injectorBindings, hasComponent
); );
} else { } else {
inheritedProtoElementInjector = parentProtoElementInjector; inheritedProtoElementInjector = parentProtoElementInjector;
@ -56,19 +58,19 @@ export class ProtoElementInjectorBuilder extends CompileStep {
return parentProtoElementInjector; return parentProtoElementInjector;
} }
_collectDirectiveTypes(pipelineElement) { _collectDirectiveBindings(pipelineElement) {
var directiveTypes = []; var directiveTypes = [];
if (isPresent(pipelineElement.componentDirective)) {
ListWrapper.push(directiveTypes, pipelineElement.componentDirective.type);
}
if (isPresent(pipelineElement.templateDirective)) {
ListWrapper.push(directiveTypes, pipelineElement.templateDirective.type);
}
if (isPresent(pipelineElement.decoratorDirectives)) { if (isPresent(pipelineElement.decoratorDirectives)) {
for (var i=0; i<pipelineElement.decoratorDirectives.length; i++) { for (var i=0; i<pipelineElement.decoratorDirectives.length; i++) {
ListWrapper.push(directiveTypes, pipelineElement.decoratorDirectives[i].type); ListWrapper.push(directiveTypes, pipelineElement.decoratorDirectives[i].type);
} }
} }
if (isPresent(pipelineElement.templateDirective)) {
ListWrapper.push(directiveTypes, pipelineElement.templateDirective.type);
}
if (isPresent(pipelineElement.componentDirective)) {
ListWrapper.push(directiveTypes, pipelineElement.componentDirective.type);
}
return directiveTypes; return directiveTypes;
} }
} }

View File

@ -167,13 +167,13 @@ export class ProtoView {
static _instantiateDirectives( static _instantiateDirectives(
injectors:List<ElementInjectors>, appInjector:Injector) { injectors:List<ElementInjectors>, appInjector:Injector) {
for (var i = 0; i < injectors.length; ++i) { for (var i = 0; i < injectors.length; ++i) {
if (injectors[i] != null) injectors[i].instantiateDirectives(appInjector); if (injectors[i] != null) injectors[i].instantiateDirectives(appInjector, null);
} }
} }
static _createElementInjector(element, parent:ElementInjector, proto:ProtoElementInjector) { static _createElementInjector(element, parent:ElementInjector, proto:ProtoElementInjector) {
//TODO: vsavkin: pass element to `proto.instantiate()` once https://github.com/angular/angular/pull/98 is merged //TODO: vsavkin: pass element to `proto.instantiate()` once https://github.com/angular/angular/pull/98 is merged
return proto.instantiate(parent, null); return proto.instantiate(parent, null, null);
} }
static _rootElementInjectors(injectors) { static _rootElementInjectors(injectors) {

View File

@ -1,5 +1,5 @@
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach} from 'test_lib/test_lib'; import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach} from 'test_lib/test_lib';
import {isBlank, FIELD, IMPLEMENTS} from 'facade/lang'; import {isBlank, isPresent, FIELD, IMPLEMENTS} from 'facade/lang';
import {ListWrapper, MapWrapper, List} from 'facade/collection'; import {ListWrapper, MapWrapper, List} from 'facade/collection';
import {ProtoElementInjector, VIEW_KEY} from 'core/compiler/element_injector'; import {ProtoElementInjector, VIEW_KEY} from 'core/compiler/element_injector';
import {Parent, Ancestor} from 'core/annotations/visibility'; import {Parent, Ancestor} from 'core/annotations/visibility';
@ -12,6 +12,10 @@ class DummyView {}
class Directive { class Directive {
} }
class SomeOtherDirective {
}
class NeedsDirective { class NeedsDirective {
@FIELD("dependency:Directive") @FIELD("dependency:Directive")
constructor(dependency:Directive){ constructor(dependency:Directive){
@ -66,13 +70,13 @@ export function main() {
return [lookupName(tree), children]; return [lookupName(tree), children];
} }
function injector(bindings, appInjector = null, props = null) { function injector(bindings, lightDomAppInjector = null, shadowDomAppInjector = null, props = null) {
if (isBlank(appInjector)) appInjector = new Injector([]); if (isBlank(lightDomAppInjector)) lightDomAppInjector = new Injector([]);
if (isBlank(props)) props = {"view" : null}; if (isBlank(props)) props = {"view": null};
var proto = new ProtoElementInjector(null, 0, bindings); var proto = new ProtoElementInjector(null, 0, bindings, isPresent(shadowDomAppInjector));
var inj = proto.instantiate(null, props["view"]); var inj = proto.instantiate(null, null, props["view"]);
inj.instantiateDirectives(appInjector); inj.instantiateDirectives(lightDomAppInjector, shadowDomAppInjector);
return inj; return inj;
} }
@ -80,16 +84,31 @@ export function main() {
var inj = new Injector([]); var inj = new Injector([]);
var protoParent = new ProtoElementInjector(null, 0, parentBindings); var protoParent = new ProtoElementInjector(null, 0, parentBindings);
var parent = protoParent.instantiate(null, null); var parent = protoParent.instantiate(null, null, null);
parent.instantiateDirectives(inj); parent.instantiateDirectives(inj, null);
var protoChild = new ProtoElementInjector(protoParent, 1, childBindings); var protoChild = new ProtoElementInjector(protoParent, 1, childBindings);
var child = protoChild.instantiate(parent, null); var child = protoChild.instantiate(parent, null, null);
child.instantiateDirectives(inj); child.instantiateDirectives(inj, null);
return child; return child;
} }
function hostShadowInjectors(hostBindings, shadowBindings) {
var inj = new Injector([]);
var shadowInj = inj.createChild([]);
var protoParent = new ProtoElementInjector(null, 0, hostBindings, true);
var host = protoParent.instantiate(null, null, null);
host.instantiateDirectives(inj, shadowInj);
var protoChild = new ProtoElementInjector(protoParent, 0, shadowBindings, false);
var shadow = protoChild.instantiate(null, host, null);
shadow.instantiateDirectives(shadowInj, null);
return shadow;
}
describe("ElementInjector", function () { describe("ElementInjector", function () {
describe("instantiate", function () { describe("instantiate", function () {
it("should create an element injector", function () { it("should create an element injector", function () {
@ -97,9 +116,9 @@ export function main() {
var protoChild1 = new ProtoElementInjector(protoParent, 1, []); var protoChild1 = new ProtoElementInjector(protoParent, 1, []);
var protoChild2 = new ProtoElementInjector(protoParent, 2, []); var protoChild2 = new ProtoElementInjector(protoParent, 2, []);
var p = protoParent.instantiate(null, null); var p = protoParent.instantiate(null, null, null);
var c1 = protoChild1.instantiate(p, null); var c1 = protoChild1.instantiate(p, null, null);
var c2 = protoChild2.instantiate(p, null); var c2 = protoChild2.instantiate(p, null, null);
expect(humanize(p, [ expect(humanize(p, [
[p, 'parent'], [p, 'parent'],
@ -147,13 +166,47 @@ export function main() {
expect(d.service).toEqual("service"); expect(d.service).toEqual("service");
}); });
it("should instantiate directives that depend on speical objects", function () { it("should instantiate directives that depend on special objects", function () {
var view = new DummyView(); var view = new DummyView();
var inj = injector([NeedsView], null, {"view" : view}); var inj = injector([NeedsView], null, null, {'view':view});
expect(inj.get(NeedsView).view).toBe(view); expect(inj.get(NeedsView).view).toBe(view);
}); });
it("should instantiate directives that depend on the containing component", function () {
var shadow = hostShadowInjectors([Directive], [NeedsDirective]);
var d = shadow.get(NeedsDirective);
expect(d).toBeAnInstanceOf(NeedsDirective);
expect(d.dependency).toBeAnInstanceOf(Directive);
});
it("should not instantiate directives that depend on other directives in the containing component's ElementInjector", () => {
expect( () => {
hostShadowInjectors([SomeOtherDirective, Directive], [NeedsDirective]);
}).toThrowError('No provider for Directive! (NeedsDirective -> Directive)')
});
it("should instantiate component directives that depend on app services in the shadow app injector", () => {
var shadowAppInjector = new Injector([
bind("service").toValue("service")
]);
var inj = injector([NeedsService], null, shadowAppInjector);
var d = inj.get(NeedsService);
expect(d).toBeAnInstanceOf(NeedsService);
expect(d.service).toEqual("service");
});
it("should not instantiate other directives that depend on app services in the shadow app injector", () => {
var shadowAppInjector = new Injector([
bind("service").toValue("service")
]);
expect( () => {
injector([SomeOtherDirective, NeedsService], null, shadowAppInjector);
}).toThrowError('No provider for service! (NeedsService -> service)');
});
it("should return app services", function () { it("should return app services", function () {
var appInjector = new Injector([ var appInjector = new Injector([
bind("service").toValue("service") bind("service").toValue("service")
@ -204,7 +257,7 @@ export function main() {
describe("special objects", function () { describe("special objects", function () {
it("should return view", function () { it("should return view", function () {
var view = new DummyView(); var view = new DummyView();
var inj = injector([], null, {"view" : view}); var inj = injector([], null, null, {"view" : view});
expect(inj.get(View)).toEqual(view); expect(inj.get(View)).toEqual(view);
}); });

View File

@ -41,9 +41,8 @@ export function main() {
}), protoElementInjectorBuilder]); }), protoElementInjectorBuilder]);
} }
function assertProtoElementInjector(protoElementInjector, parent, index, bindings) { function getCreationArgs(protoElementInjector) {
var args = protoElementInjectorBuilder.findArgsFor(protoElementInjector); return protoElementInjectorBuilder.findArgsFor(protoElementInjector);
expect(args).toEqual([parent, index, bindings]);
} }
it('should not create a ProtoElementInjector for elements without directives', () => { it('should not create a ProtoElementInjector for elements without directives', () => {
@ -51,10 +50,19 @@ export function main() {
expect(results[0].inheritedProtoElementInjector).toBe(null); expect(results[0].inheritedProtoElementInjector).toBe(null);
}); });
it('should create a ProtoElementInjector for elements with directives', () => { it('should create a ProtoElementInjector for elements directives', () => {
var directives = [SomeDecoratorDirective, SomeTemplateDirective, SomeComponentDirective]; var directives = [SomeComponentDirective, SomeTemplateDirective, SomeDecoratorDirective];
var results = createPipeline(directives).process(createElement('<div directives></div>')); var results = createPipeline(directives).process(createElement('<div directives></div>'));
assertProtoElementInjector(results[0].inheritedProtoElementInjector, null, 0, directives); var creationArgs = getCreationArgs(results[0].inheritedProtoElementInjector);
expect(creationArgs['bindings']).toEqual(directives);
});
it('should mark ProtoElementInjector for elements with component directives and use the ComponentDirective as first binding', () => {
var directives = [SomeDecoratorDirective, SomeComponentDirective];
var results = createPipeline(directives).process(createElement('<div directives></div>'));
var creationArgs = getCreationArgs(results[0].inheritedProtoElementInjector);
expect(creationArgs['firstBindingIsComponent']).toBe(true);
expect(creationArgs['bindings']).toEqual([SomeComponentDirective, SomeDecoratorDirective]);
}); });
it('should use the next ElementBinder index as index of the ProtoElementInjector', () => { it('should use the next ElementBinder index as index of the ProtoElementInjector', () => {
@ -63,37 +71,36 @@ export function main() {
ListWrapper.push(protoView.elementBinders, null); ListWrapper.push(protoView.elementBinders, null);
var directives = [SomeDecoratorDirective]; var directives = [SomeDecoratorDirective];
var results = createPipeline(directives).process(createElement('<div directives></div>')); var results = createPipeline(directives).process(createElement('<div directives></div>'));
assertProtoElementInjector( var creationArgs = getCreationArgs(results[0].inheritedProtoElementInjector);
results[0].inheritedProtoElementInjector, null, protoView.elementBinders.length, directives); expect(creationArgs['index']).toBe(protoView.elementBinders.length);
}); });
it('should inherit the ProtoElementInjector down to children without directives', () => { it('should inherit the ProtoElementInjector down to children without directives', () => {
var directives = [SomeDecoratorDirective, SomeTemplateDirective, SomeComponentDirective]; var directives = [SomeDecoratorDirective];
var results = createPipeline(directives).process(createElement('<div directives><span></span></div>')); var results = createPipeline(directives).process(createElement('<div directives><span></span></div>'));
assertProtoElementInjector(results[0].inheritedProtoElementInjector, null, 0, directives); expect(results[1].inheritedProtoElementInjector).toBe(results[0].inheritedProtoElementInjector);
assertProtoElementInjector(results[1].inheritedProtoElementInjector, null, 0, directives);
}); });
it('should use the ProtoElementInjector of the parent element as parent', () => { it('should use the ProtoElementInjector of the parent element as parent', () => {
var el = createElement('<div directives><span><a directives></a></span></div>'); var el = createElement('<div directives><span><a directives></a></span></div>');
var directives = [SomeDecoratorDirective, SomeTemplateDirective, SomeComponentDirective]; var directives = [SomeDecoratorDirective];
var results = createPipeline(directives).process(el); var results = createPipeline(directives).process(el);
assertProtoElementInjector(results[2].inheritedProtoElementInjector, expect(results[2].inheritedProtoElementInjector.parent).toBe(
results[0].inheritedProtoElementInjector, 0, directives); results[0].inheritedProtoElementInjector);
}); });
it('should use a null parent for viewRoots', () => { it('should use a null parent for viewRoots', () => {
var el = createElement('<div directives><span viewroot directives></span></div>'); var el = createElement('<div directives><span viewroot directives></span></div>');
var directives = [SomeDecoratorDirective, SomeTemplateDirective, SomeComponentDirective]; var directives = [SomeDecoratorDirective];
var results = createPipeline(directives).process(el); var results = createPipeline(directives).process(el);
assertProtoElementInjector(results[1].inheritedProtoElementInjector, null, 0, directives); expect(results[1].inheritedProtoElementInjector.parent).toBe(null);
}); });
it('should use a null parent if there is an intermediate viewRoot', () => { it('should use a null parent if there is an intermediate viewRoot', () => {
var el = createElement('<div directives><span viewroot><a directives></a></span></div>'); var el = createElement('<div directives><span viewroot><a directives></a></span></div>');
var directives = [SomeDecoratorDirective, SomeTemplateDirective, SomeComponentDirective]; var directives = [SomeDecoratorDirective];
var results = createPipeline(directives).process(el); var results = createPipeline(directives).process(el);
assertProtoElementInjector(results[2].inheritedProtoElementInjector, null, 0, directives); expect(results[2].inheritedProtoElementInjector.parent).toBe(null);
}); });
}); });
} }
@ -111,10 +118,10 @@ class TestableProtoElementInjectorBuilder extends ProtoElementInjectorBuilder {
} }
return null; return null;
} }
internalCreateProtoElementInjector(parent, index, directives) { internalCreateProtoElementInjector(parent, index, bindings, firstBindingIsComponent) {
var result = new ProtoElementInjector(parent, index, directives); var result = new ProtoElementInjector(parent, index, bindings, firstBindingIsComponent);
ListWrapper.push(this.debugObjects, result); ListWrapper.push(this.debugObjects, result);
ListWrapper.push(this.debugObjects, [parent, index, directives]); ListWrapper.push(this.debugObjects, {'parent': parent, 'index': index, 'bindings': bindings, 'firstBindingIsComponent': firstBindingIsComponent});
return result; return result;
} }
} }
@ -128,10 +135,14 @@ class MockStep extends CompileStep {
} }
} }
class SomeComponentService {}
@Template() @Template()
class SomeTemplateDirective {} class SomeTemplateDirective {}
@Component() @Component({
componentServices: [SomeComponentService]
})
class SomeComponentDirective {} class SomeComponentDirective {}
@Decorator() @Decorator()