diff --git a/modules/core/src/compiler/element_injector.js b/modules/core/src/compiler/element_injector.js index dc883c8625..cc990d915f 100644 --- a/modules/core/src/compiler/element_injector.js +++ b/modules/core/src/compiler/element_injector.js @@ -165,9 +165,11 @@ export class ProtoElementInjector { parent:ProtoElementInjector; index:int; view:View; - constructor(parent:ProtoElementInjector, index:int, bindings:List, firstBindingIsComponent:boolean = false) { + distanceToParent:number; + constructor(parent:ProtoElementInjector, index:int, bindings:List, firstBindingIsComponent:boolean = false, distanceToParent:number = 0) { this.parent = parent; this.index = index; + this.distanceToParent = distanceToParent; this._binding0IsComponent = firstBindingIsComponent; this._binding0 = null; this._keyId0 = null; @@ -331,6 +333,10 @@ export class ElementInjector extends TreeNode { } } + directParent(): ElementInjector { + return this._proto.distanceToParent < 2 ? this.parent : null; + } + _isComponentKey(key:Key) { return this._proto._binding0IsComponent && key.id === this._proto._keyId0; } @@ -396,11 +402,11 @@ export class ElementInjector extends TreeNode { * * Write benchmarks before doing this optimization. */ - _getByKey(key:Key, depth:int, requestor:Key) { + _getByKey(key:Key, depth:number, requestor:Key) { var ei = this; if (! this._shouldIncludeSelf(depth)) { - depth -= 1; + depth -= ei._proto.distanceToParent; ei = ei._parent; } @@ -411,8 +417,8 @@ export class ElementInjector extends TreeNode { var dir = ei._getDirectiveByKeyId(key.id); if (dir !== _undefined) return dir; + depth -= ei._proto.distanceToParent; ei = ei._parent; - depth -= 1; } if (isPresent(this._host) && this._host._isComponentKey(key)) { @@ -440,7 +446,7 @@ export class ElementInjector extends TreeNode { if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element; if (keyId === staticKeys.viewPortId) return this._preBuiltObjects.viewPort; if (keyId === staticKeys.destinationLightDomId) { - var p:ElementInjector = this._parent; + var p:ElementInjector = this.directParent(); return isPresent(p) ? p._preBuiltObjects.lightDom : null; } if (keyId === staticKeys.sourceLightDomId) { diff --git a/modules/core/src/compiler/pipeline/compile_element.js b/modules/core/src/compiler/pipeline/compile_element.js index 46a9c70ca3..32e3319bfe 100644 --- a/modules/core/src/compiler/pipeline/compile_element.js +++ b/modules/core/src/compiler/pipeline/compile_element.js @@ -32,6 +32,7 @@ export class CompileElement { inheritedProtoView:ProtoView; inheritedProtoElementInjector:ProtoElementInjector; inheritedElementBinder:ElementBinder; + distanceToParentInjector:number; compileChildren: boolean; constructor(element:Element) { this.element = element; @@ -55,6 +56,7 @@ export class CompileElement { // inherited down to children if they don't have // an own elementBinder this.inheritedElementBinder = null; + this.distanceToParentInjector = 0; this.compileChildren = true; } diff --git a/modules/core/src/compiler/pipeline/proto_element_injector_builder.js b/modules/core/src/compiler/pipeline/proto_element_injector_builder.js index 27dd7ffd21..aaecf5a9e5 100644 --- a/modules/core/src/compiler/pipeline/proto_element_injector_builder.js +++ b/modules/core/src/compiler/pipeline/proto_element_injector_builder.js @@ -24,14 +24,15 @@ import {CompileControl} from './compile_control'; */ export class ProtoElementInjectorBuilder extends CompileStep { // public so that we can overwrite it in tests - internalCreateProtoElementInjector(parent, index, directives, firstBindingIsComponent) { - return new ProtoElementInjector(parent, index, directives, firstBindingIsComponent); + internalCreateProtoElementInjector(parent, index, directives, firstBindingIsComponent, distance) { + return new ProtoElementInjector(parent, index, directives, firstBindingIsComponent, distance); } process(parent:CompileElement, current:CompileElement, control:CompileControl) { - var inheritedProtoElementInjector = null; + var distanceToParentInjector = this._getDistanceToParentInjector(parent, current); var parentProtoElementInjector = this._getParentProtoElementInjector(parent, current); var injectorBindings = this._collectDirectiveBindings(current); + // TODO: add lightDomServices as well, // but after the directives as we rely on that order // in the element_binder_builder. @@ -39,13 +40,21 @@ export class ProtoElementInjectorBuilder extends CompileStep { if (injectorBindings.length > 0) { var protoView = current.inheritedProtoView; var hasComponent = isPresent(current.componentDirective); - inheritedProtoElementInjector = this.internalCreateProtoElementInjector( - parentProtoElementInjector, protoView.elementBinders.length, injectorBindings, hasComponent + + current.inheritedProtoElementInjector = this.internalCreateProtoElementInjector( + parentProtoElementInjector, protoView.elementBinders.length, injectorBindings, + hasComponent, distanceToParentInjector ); + current.distanceToParentInjector = 0; + } else { - inheritedProtoElementInjector = parentProtoElementInjector; + current.inheritedProtoElementInjector = parentProtoElementInjector; + current.distanceToParentInjector = distanceToParentInjector; } - current.inheritedProtoElementInjector = inheritedProtoElementInjector; + } + + _getDistanceToParentInjector(parent, current) { + return isPresent(parent) ? parent.distanceToParentInjector + 1 : 0; } _getParentProtoElementInjector(parent, current) { diff --git a/modules/core/test/compiler/element_injector_spec.js b/modules/core/test/compiler/element_injector_spec.js index cfa8551482..5df327704a 100644 --- a/modules/core/test/compiler/element_injector_spec.js +++ b/modules/core/test/compiler/element_injector_spec.js @@ -103,7 +103,7 @@ export function main() { parent.instantiateDirectives(inj, null, parentPreBuildObjects); - var protoChild = new ProtoElementInjector(protoParent, 1, childBindings); + var protoChild = new ProtoElementInjector(protoParent, 1, childBindings, false, 1); var child = protoChild.instantiate(parent, null); child.instantiateDirectives(inj, null, defaultPreBuiltObjects); @@ -120,7 +120,7 @@ export function main() { var host = protoParent.instantiate(null, null); host.instantiateDirectives(inj, shadowInj, hostPreBuildObjects); - var protoChild = new ProtoElementInjector(protoParent, 0, shadowBindings, false); + var protoChild = new ProtoElementInjector(protoParent, 0, shadowBindings, false, 1); var shadow = protoChild.instantiate(null, host); shadow.instantiateDirectives(shadowInj, null, null); @@ -144,6 +144,30 @@ export function main() { [c2, 'child2'] ])).toEqual(["parent", ["child1", "child2"]]); }); + + describe("direct parent", () => { + it("should return parent injector when distance is 1", () => { + var distance = 1; + var protoParent = new ProtoElementInjector(null, 0, []); + var protoChild = new ProtoElementInjector(protoParent, 1, [], false, distance); + + var p = protoParent.instantiate(null, null); + var c = protoChild.instantiate(p, null); + + expect(c.directParent()).toEqual(p); + }); + + it("should return null otherwise", () => { + var distance = 2; + var protoParent = new ProtoElementInjector(null, 0, []); + var protoChild = new ProtoElementInjector(protoParent, 1, [], false, distance); + + var p = protoParent.instantiate(null, null); + var c = protoChild.instantiate(p, null); + + expect(c.directParent()).toEqual(null); + }); + }); }); describe("hasBindings", function () { diff --git a/modules/core/test/compiler/integration_spec.js b/modules/core/test/compiler/integration_spec.js index 74cbc2b023..032a639690 100644 --- a/modules/core/test/compiler/integration_spec.js +++ b/modules/core/test/compiler/integration_spec.js @@ -128,7 +128,6 @@ export function main() { expect(DOM.getText(view.nodes[0])).toEqual('Before LightDOM After'); done(); }); - }); }); } diff --git a/modules/core/test/compiler/pipeline/proto_element_injector_builder_spec.js b/modules/core/test/compiler/pipeline/proto_element_injector_builder_spec.js index 04cc9727c6..9ae2ef0a11 100644 --- a/modules/core/test/compiler/pipeline/proto_element_injector_builder_spec.js +++ b/modules/core/test/compiler/pipeline/proto_element_injector_builder_spec.js @@ -73,32 +73,57 @@ export function main() { expect(creationArgs['index']).toBe(protoView.elementBinders.length); }); - it('should inherit the ProtoElementInjector down to children without directives', () => { - var directives = [SomeDecoratorDirective]; - var results = createPipeline(directives).process(createElement('
')); - expect(results[1].inheritedProtoElementInjector).toBe(results[0].inheritedProtoElementInjector); + describe("inheritedProtoElementInjector", () => { + it('should inherit the ProtoElementInjector down to children without directives', () => { + var directives = [SomeDecoratorDirective]; + var results = createPipeline(directives).process(createElement('
')); + expect(results[1].inheritedProtoElementInjector).toBe(results[0].inheritedProtoElementInjector); + }); + + it('should use the ProtoElementInjector of the parent element as parent', () => { + var el = createElement('
'); + var directives = [SomeDecoratorDirective]; + var results = createPipeline(directives).process(el); + expect(results[2].inheritedProtoElementInjector.parent).toBe( + results[0].inheritedProtoElementInjector); + }); + + it('should use a null parent for viewRoots', () => { + var el = createElement('
'); + var directives = [SomeDecoratorDirective]; + var results = createPipeline(directives).process(el); + expect(results[1].inheritedProtoElementInjector.parent).toBe(null); + }); + + it('should use a null parent if there is an intermediate viewRoot', () => { + var el = createElement('
'); + var directives = [SomeDecoratorDirective]; + var results = createPipeline(directives).process(el); + expect(results[2].inheritedProtoElementInjector.parent).toBe(null); + }); }); - it('should use the ProtoElementInjector of the parent element as parent', () => { - var el = createElement('
'); - var directives = [SomeDecoratorDirective]; - var results = createPipeline(directives).process(el); - expect(results[2].inheritedProtoElementInjector.parent).toBe( - results[0].inheritedProtoElementInjector); - }); + describe("distanceToParentInjector", () => { + it("should be 0 for root elements", () => { + var el = createElement('
'); + var directives = [SomeDecoratorDirective]; + var results = createPipeline(directives).process(el); + expect(results[0].inheritedProtoElementInjector.distanceToParent).toBe(0); + }); - it('should use a null parent for viewRoots', () => { - var el = createElement('
'); - var directives = [SomeDecoratorDirective]; - var results = createPipeline(directives).process(el); - expect(results[1].inheritedProtoElementInjector.parent).toBe(null); - }); + it("should be 1 when a parent element has an injector", () => { + var el = createElement('
'); + var directives = [SomeDecoratorDirective]; + var results = createPipeline(directives).process(el); + expect(results[1].inheritedProtoElementInjector.distanceToParent).toBe(1); + }); - it('should use a null parent if there is an intermediate viewRoot', () => { - var el = createElement('
'); - var directives = [SomeDecoratorDirective]; - var results = createPipeline(directives).process(el); - expect(results[2].inheritedProtoElementInjector.parent).toBe(null); + it("should add 1 for every element that does not have an injector", () => { + var el = createElement('
'); + var directives = [SomeDecoratorDirective]; + var results = createPipeline(directives).process(el); + expect(results[3].inheritedProtoElementInjector.distanceToParent).toBe(3); + }); }); }); } @@ -117,8 +142,8 @@ class TestableProtoElementInjectorBuilder extends ProtoElementInjectorBuilder { } return null; } - internalCreateProtoElementInjector(parent, index, bindings, firstBindingIsComponent) { - var result = new ProtoElementInjector(parent, index, bindings, firstBindingIsComponent); + internalCreateProtoElementInjector(parent, index, bindings, firstBindingIsComponent, distance) { + var result = new ProtoElementInjector(parent, index, bindings, firstBindingIsComponent, distance); ListWrapper.push(this.debugObjects, result); ListWrapper.push(this.debugObjects, {'parent': parent, 'index': index, 'bindings': bindings, 'firstBindingIsComponent': firstBindingIsComponent}); return result;