From 57496926cad0d840092c7623079883983ca07d53 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Fri, 17 Jul 2015 15:29:05 -0700 Subject: [PATCH] fix(di): fixed dynamic component loading of components created in child injector --- .../src/core/compiler/element_injector.ts | 69 ++++++++----------- .../core/compiler/element_injector_spec.ts | 34 +++++++-- 2 files changed, 54 insertions(+), 49 deletions(-) diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index 043f05fbfe..2092a1a534 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -476,7 +476,7 @@ export class ElementInjector extends TreeNode implements Depend this._host = host; this._preBuiltObjects = preBuiltObjects; - this._reattachInjectors(imperativelyCreatedInjector, host); + this._reattachInjectors(imperativelyCreatedInjector); this._strategy.hydrate(); if (isPresent(host)) { @@ -489,52 +489,37 @@ export class ElementInjector extends TreeNode implements Depend this.hydrated = true; } - private _reattachInjectors(imperativelyCreatedInjector: Injector, host: ElementInjector): void { + private _reattachInjectors(imperativelyCreatedInjector: Injector): void { + // Dynamically-loaded component in the template. Not a root ElementInjector. if (isPresent(this._parent)) { - this._reattachInjector(this._injector, this._parent._injector, false); - } else { - // This injector is at the boundary. - // - // The injector tree we are assembling: - // - // host._injector (only if present) - // | - // |boundary - // | - // imperativelyCreatedInjector (only if present) - // | - // |boundary - // | - // this._injector - // - - // host._injector (only if present) - // | - // |boundary - // | - // imperativelyCreatedInjector (only if present) - if (isPresent(imperativelyCreatedInjector) && isPresent(host)) { - this._reattachInjector(imperativelyCreatedInjector, host._injector, true); + if (isPresent(imperativelyCreatedInjector)) { + // The imperative injector is similar to having an element between + // the dynamic-loaded component and its parent => no boundaries. + this._reattachInjector(this._injector, imperativelyCreatedInjector, false); + this._reattachInjector(imperativelyCreatedInjector, this._parent._injector, false); + } else { + this._reattachInjector(this._injector, this._parent._injector, false); } - // host._injector OR imperativelyCreatedInjector OR null - // | - // |boundary - // | - // this._injector - var parent = this._closestBoundaryInjector(imperativelyCreatedInjector, host); - this._reattachInjector(this._injector, parent, true); - } - } + // Dynamically-loaded component in the template. A root ElementInjector. + } else if (isPresent(this._host)) { + // The imperative injector is similar to having an element between + // the dynamic-loaded component and its parent => no boundary between + // the component and imperativelyCreatedInjector. + // But since it is a root ElementInjector, we need to create a boundary + // between imperativelyCreatedInjector and _host. + if (isPresent(imperativelyCreatedInjector)) { + this._reattachInjector(this._injector, imperativelyCreatedInjector, false); + this._reattachInjector(imperativelyCreatedInjector, this._host._injector, true); + } else { + this._reattachInjector(this._injector, this._host._injector, true); + } - private _closestBoundaryInjector(imperativelyCreatedInjector: Injector, - host: ElementInjector): Injector { - if (isPresent(imperativelyCreatedInjector)) { - return imperativelyCreatedInjector; - } else if (isPresent(host)) { - return host._injector; + // Bootstrap } else { - return null; + if (isPresent(imperativelyCreatedInjector)) { + this._reattachInjector(this._injector, imperativelyCreatedInjector, true); + } } } diff --git a/modules/angular2/test/core/compiler/element_injector_spec.ts b/modules/angular2/test/core/compiler/element_injector_spec.ts index fd5108b587..ef06b3c315 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.ts +++ b/modules/angular2/test/core/compiler/element_injector_spec.ts @@ -247,7 +247,7 @@ export function main() { for (var i = 0; i < 20; i++) { dynamicBindings.push(bind(i).toValue(i)); - } + } function createPei(parent, index, bindings, distance = 1, hasShadowRoot = false, dirVariableBindings = null) { var directiveBinding = ListWrapper.map(bindings, b => { @@ -278,7 +278,7 @@ export function main() { return inj; } - function parentChildInjectors(parentBindings, childBindings, parentPreBuildObjects = null) { + function parentChildInjectors(parentBindings, childBindings, parentPreBuildObjects = null, imperativelyCreatedInjector = null) { if (isBlank(parentPreBuildObjects)) parentPreBuildObjects = defaultPreBuiltObjects; var protoParent = createPei(null, 0, parentBindings); @@ -288,20 +288,20 @@ export function main() { var protoChild = createPei(protoParent, 1, childBindings, 1, false); var child = protoChild.instantiate(parent); - child.hydrate(null, null, defaultPreBuiltObjects); + child.hydrate(imperativelyCreatedInjector, null, defaultPreBuiltObjects); return child; } function hostShadowInjectors(hostBindings: List, - shadowBindings: List): ElementInjector { + shadowBindings: List, imperativelyCreatedInjector = null): ElementInjector { var protoHost = createPei(null, 0, hostBindings, 0, true); var host = protoHost.instantiate(null); host.hydrate(null, null, defaultPreBuiltObjects); var protoShadow = createPei(null, 0, shadowBindings, 0, false); var shadow = protoShadow.instantiate(null); - shadow.hydrate(null, host, null); + shadow.hydrate(imperativelyCreatedInjector, host, null); return shadow; } @@ -715,12 +715,32 @@ export function main() { expect(shadowInj.get(NeedsService).service).toEqual('hostService'); }); - it("should instantiate directives that depend on imperativley created injector bindings", () => { + it("should instantiate directives that depend on imperatively created injector bindings (bootstrap)", () => { var imperativelyCreatedInjector = Injector.resolveAndCreate([ bind("service").toValue('appService') ]); var inj = injector([NeedsService], imperativelyCreatedInjector); expect(inj.get(NeedsService).service).toEqual('appService'); + + expect(() => injector([NeedsAncestorService], imperativelyCreatedInjector)).toThrowError(); + }); + + it("should instantiate directives that depend on imperatively created injector bindings (root injector)", () => { + var imperativelyCreatedInjector = Injector.resolveAndCreate([ + bind("service").toValue('appService') + ]); + var inj = hostShadowInjectors([SimpleDirective], [NeedsService, NeedsAncestorService], imperativelyCreatedInjector); + expect(inj.get(NeedsService).service).toEqual('appService'); + expect(inj.get(NeedsAncestorService).service).toEqual('appService'); + }); + + it("should instantiate directives that depend on imperatively created injector bindings (child injector)", () => { + var imperativelyCreatedInjector = Injector.resolveAndCreate([ + bind("service").toValue('appService') + ]); + var inj = parentChildInjectors([], [NeedsService, NeedsAncestorService], null, imperativelyCreatedInjector); + expect(inj.get(NeedsService).service).toEqual('appService'); + expect(inj.get(NeedsAncestorService).service).toEqual('appService'); }); it("should prioritize viewInjector over hostInjector for the same binding", () => { @@ -1192,7 +1212,7 @@ export function main() { }); }); }); - }); + }); } class ContextWithHandler {