From 1130e48541406823df6afecf8475b86cbca5e32a Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Fri, 26 Oct 2018 19:12:24 -0700 Subject: [PATCH] fix(ivy): host injection flag should not throw for embedded views (#26795) PR Close #26795 --- packages/core/src/render3/di.ts | 14 ++- .../core/src/render3/interfaces/injector.ts | 3 +- packages/core/test/render3/di_spec.ts | 116 +++++++++++++++--- 3 files changed, 110 insertions(+), 23 deletions(-) diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index d967b48e20..48cb229e5c 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -21,7 +21,7 @@ import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TN import {DECLARATION_VIEW, HOST_NODE, INJECTOR, LViewData, TData, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes} from './node_assert'; import {getPreviousOrParentTNode, getViewData, setTNodeAndViewData} from './state'; -import {getParentInjectorIndex, getParentInjectorView, getParentInjectorViewOffset, hasParentInjector, isComponent, stringify} from './util'; +import {getParentInjectorIndex, getParentInjectorView, hasParentInjector, isComponent, stringify} from './util'; /** * Defines if the call to `inject` should include `viewProviders` in its resolution. @@ -196,7 +196,7 @@ export function getInjectorIndex(tNode: TNode, hostView: LViewData): number { */ export function getParentInjectorLocation(tNode: TNode, view: LViewData): RelativeInjectorLocation { if (tNode.parent && tNode.parent.injectorIndex !== -1) { - return tNode.parent.injectorIndex as any; // view offset is 0 + return tNode.parent.injectorIndex as any; // ViewOffset is 0, AcrossHostBoundary is 0 } // For most cases, the parent injector index can be found on the host node (e.g. for component @@ -209,8 +209,13 @@ export function getParentInjectorLocation(tNode: TNode, view: LViewData): Relati hostTNode = view[HOST_NODE] !; viewOffset++; } + const acrossHostBoundary = hostTNode && hostTNode.type === TNodeType.Element ? + RelativeInjectorLocationFlags.AcrossHostBoundary : + 0; + return hostTNode ? - hostTNode.injectorIndex | (viewOffset << RelativeInjectorLocationFlags.ViewOffsetShift) : + hostTNode.injectorIndex | (viewOffset << RelativeInjectorLocationFlags.ViewOffsetShift) | + acrossHostBoundary : -1 as any; } @@ -507,7 +512,8 @@ function shouldSearchParent(flags: InjectFlags, parentLocation: RelativeInjector number { return !( flags & InjectFlags.Self || - (flags & InjectFlags.Host && getParentInjectorViewOffset(parentLocation) > 0)); + (flags & InjectFlags.Host && + ((parentLocation as any as number) & RelativeInjectorLocationFlags.AcrossHostBoundary))); } export function injectInjector() { diff --git a/packages/core/src/render3/interfaces/injector.ts b/packages/core/src/render3/interfaces/injector.ts index 0bf4301dad..b49f94cf69 100644 --- a/packages/core/src/render3/interfaces/injector.ts +++ b/packages/core/src/render3/interfaces/injector.ts @@ -26,7 +26,8 @@ export interface RelativeInjectorLocation { __brand__: 'RelativeInjectorLocation export const enum RelativeInjectorLocationFlags { InjectorIndexMask = 0b111111111111111, - ViewOffsetShift = 15, + AcrossHostBoundary = 0b1000000000000000, + ViewOffsetShift = 16, NO_PARENT = -1, } diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 2675c8595c..d0fca9879a 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -1010,8 +1010,10 @@ describe('di', () => { }).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/); }); - it('should not pass component boundary with @Host', () => { - let dirA: DirA; + describe('@Host', () => { + let dirA: DirA|null = null; + + beforeEach(() => dirA = null); class DirA { constructor(@Host() public dirB: DirB) {} @@ -1023,28 +1025,106 @@ describe('di', () => { }); } - /**
*/ - const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - element(0, 'div', ['dirA', '']); + it('should not find providers across component boundaries', () => { + /**
*/ + const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + element(0, 'div', ['dirA', '']); + } + }, 1, 0, [DirA, DirB]); + + /* */ + const App = createComponent('app', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + element(0, 'comp', ['dirB', '']); + } + }, 1, 0, [Comp, DirB]); + + expect(() => { + new ComponentFixture(App); + }).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/); + }); + + it('should not find providers across component boundaries if in inline view', () => { + let comp !: any; + + /** + * % if (showing) { + *
+ * % } + */ + const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + container(0); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(0); + { + if (ctx.showing) { + let rf1 = embeddedViewStart(0, 1, 0); + if (rf1 & RenderFlags.Create) { + element(0, 'div', ['dirA', '']); + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + } + }, 1, 0, [DirA, DirB]); + + /* */ + const App = createComponent('app', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + element(0, 'comp', ['dirB', '']); + } + if (rf & RenderFlags.Update) { + comp = getDirectiveOnNode(0); + } + }, 1, 0, [Comp, DirB]); + + const fixture = new ComponentFixture(App); + expect(() => { + comp.showing = true; + fixture.update(); + }).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/); + }); + + it('should find providers across embedded views if not passing component boundary', () => { + let dirB !: DirB; + + function IfTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + element(0, 'div', ['dirA', '']); + } } - }, 1, 0, [DirA, DirB]); - /* */ - const App = createComponent('app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - element(0, 'comp', ['dirB', '']); - } - }, 1, 0, [Comp, DirB]); + /** + *
+ *
+ *
+ */ + const App = createComponent('app', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', ['dirB', '']); + { template(1, IfTemplate, 1, 0, '', ['ngIf', '']); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + elementProperty(1, 'ngIf', bind(ctx.showing)); - expect(() => { - new ComponentFixture(App); - }).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/); + // testing only + dirB = getDirectiveOnNode(0); + } + }, 2, 1, [NgIf, DirA, DirB]); + const fixture = new ComponentFixture(App); + fixture.component.showing = true; + fixture.update(); + + expect(dirA !.dirB).toEqual(dirB); + }); }); - }); - }); describe('Special tokens', () => {