From b8ffcf973cb108c89f738c009313c352f0e2ac2a Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Tue, 28 Jan 2020 10:22:17 -0800 Subject: [PATCH] fix(ivy): update ViewContainerRef to get the correct parentInjector (#35013) PR Close #35013 --- packages/core/src/render3/node_util.ts | 17 ++++--- packages/core/test/acceptance/di_spec.ts | 58 ++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/packages/core/src/render3/node_util.ts b/packages/core/src/render3/node_util.ts index 1640aade65..8f750ea399 100644 --- a/packages/core/src/render3/node_util.ts +++ b/packages/core/src/render3/node_util.ts @@ -12,8 +12,10 @@ import {DECLARATION_VIEW, LView, T_HOST} from './interfaces/view'; import {getParentInjectorViewOffset} from './util/injector_utils'; /** - * Unwraps a parent injector location number to find the view offset from the current injector, - * then walks up the declaration view tree until the TNode of the parent injector is found. + * If `startTNode.parent` exists and has an injector, returns TNode for that injector. + * Otherwise, unwraps a parent injector location number to find the view offset from the current + * injector, then walks up the declaration view tree until the TNode of the parent injector is + * found. * * @param location The location of the parent injector, which contains the view offset * @param startView The LView instance from which to start walking up the view tree @@ -23,14 +25,17 @@ import {getParentInjectorViewOffset} from './util/injector_utils'; export function getParentInjectorTNode( location: RelativeInjectorLocation, startView: LView, startTNode: TNode): TElementNode| TContainerNode|null { + // If there is an injector on the parent TNode, retrieve the TNode for that injector. if (startTNode.parent && startTNode.parent.injectorIndex !== -1) { // view offset is 0 const injectorIndex = startTNode.parent.injectorIndex; - let parentTNode = startTNode.parent; - while (parentTNode.parent != null && injectorIndex == parentTNode.injectorIndex) { - parentTNode = parentTNode.parent; + let tNode = startTNode.parent; + // If tNode.injectorIndex === tNode.parent.injectorIndex, then the index belongs to a parent + // injector. + while (tNode.parent != null && injectorIndex == tNode.parent.injectorIndex) { + tNode = tNode.parent; } - return parentTNode; + return tNode; } let viewOffset = getParentInjectorViewOffset(location); // view offset is 1 diff --git a/packages/core/test/acceptance/di_spec.ts b/packages/core/test/acceptance/di_spec.ts index 83faaac385..fa8ed2783c 100644 --- a/packages/core/test/acceptance/di_spec.ts +++ b/packages/core/test/acceptance/di_spec.ts @@ -470,6 +470,64 @@ describe('di', () => { fixture.detectChanges(); expect(divElement.id).toBe('bar'); }); + + it('dynamic components should find dependencies when parent is projected', () => { + @Directive({selector: '[dirA]'}) + class DirA { + } + @Directive({selector: '[dirB]'}) + class DirB { + } + @Component({selector: 'child', template: ''}) + class Child { + constructor(@Optional() readonly dirA: DirA, @Optional() readonly dirB: DirB) {} + } + @Component({ + selector: 'projector', + template: '', + }) + class Projector { + } + + @Component({ + template: ` + +
+ + +
+
`, + entryComponents: [Child] + }) + class MyApp { + @ViewChild('childOrigin', {read: ViewContainerRef, static: true}) + childOrigin !: ViewContainerRef; + @ViewChild('childOriginWithDirB', {read: ViewContainerRef, static: true}) + childOriginWithDirB !: ViewContainerRef; + childFactory = this.resolver.resolveComponentFactory(Child); + + constructor(readonly resolver: ComponentFactoryResolver, readonly injector: Injector) {} + + addChild() { return this.childOrigin.createComponent(this.childFactory); } + addChildWithDirB() { return this.childOriginWithDirB.createComponent(this.childFactory); } + } + + const fixture = + TestBed.configureTestingModule({declarations: [Child, DirA, DirB, Projector, MyApp]}) + .createComponent(MyApp); + const child = fixture.componentInstance.addChild(); + expect(child).toBeDefined(); + expect(child.instance.dirA) + .not.toBeNull('dirA should be found. It is on the parent of the viewContainerRef.'); + const child2 = fixture.componentInstance.addChildWithDirB(); + expect(child2).toBeDefined(); + expect(child2.instance.dirA) + .not.toBeNull('dirA should be found. It is on the parent of the viewContainerRef.'); + expect(child2.instance.dirB) + .toBeNull( + 'dirB appears on the ng-container and should not be found because the ' + + 'viewContainerRef.createComponent node is inserted next to the container.'); + }); }); it('should throw if directive is not found anywhere', () => {