fix(ivy): host injection flag should not throw for embedded views (#26795)

PR Close #26795
This commit is contained in:
Kara Erickson 2018-10-26 19:12:24 -07:00 committed by Matias Niemelä
parent 2a869271f6
commit 1130e48541
3 changed files with 110 additions and 23 deletions

View File

@ -21,7 +21,7 @@ import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TN
import {DECLARATION_VIEW, HOST_NODE, INJECTOR, LViewData, TData, TVIEW, TView} from './interfaces/view'; import {DECLARATION_VIEW, HOST_NODE, INJECTOR, LViewData, TData, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes} from './node_assert'; import {assertNodeOfPossibleTypes} from './node_assert';
import {getPreviousOrParentTNode, getViewData, setTNodeAndViewData} from './state'; 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. * 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 { export function getParentInjectorLocation(tNode: TNode, view: LViewData): RelativeInjectorLocation {
if (tNode.parent && tNode.parent.injectorIndex !== -1) { 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 // 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] !; hostTNode = view[HOST_NODE] !;
viewOffset++; viewOffset++;
} }
const acrossHostBoundary = hostTNode && hostTNode.type === TNodeType.Element ?
RelativeInjectorLocationFlags.AcrossHostBoundary :
0;
return hostTNode ? return hostTNode ?
hostTNode.injectorIndex | (viewOffset << RelativeInjectorLocationFlags.ViewOffsetShift) : hostTNode.injectorIndex | (viewOffset << RelativeInjectorLocationFlags.ViewOffsetShift) |
acrossHostBoundary :
-1 as any; -1 as any;
} }
@ -507,7 +512,8 @@ function shouldSearchParent(flags: InjectFlags, parentLocation: RelativeInjector
number { number {
return !( return !(
flags & InjectFlags.Self || flags & InjectFlags.Self ||
(flags & InjectFlags.Host && getParentInjectorViewOffset(parentLocation) > 0)); (flags & InjectFlags.Host &&
((parentLocation as any as number) & RelativeInjectorLocationFlags.AcrossHostBoundary)));
} }
export function injectInjector() { export function injectInjector() {

View File

@ -26,7 +26,8 @@ export interface RelativeInjectorLocation { __brand__: 'RelativeInjectorLocation
export const enum RelativeInjectorLocationFlags { export const enum RelativeInjectorLocationFlags {
InjectorIndexMask = 0b111111111111111, InjectorIndexMask = 0b111111111111111,
ViewOffsetShift = 15, AcrossHostBoundary = 0b1000000000000000,
ViewOffsetShift = 16,
NO_PARENT = -1, NO_PARENT = -1,
} }

View File

@ -1010,8 +1010,10 @@ describe('di', () => {
}).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/); }).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/);
}); });
it('should not pass component boundary with @Host', () => { describe('@Host', () => {
let dirA: DirA; let dirA: DirA|null = null;
beforeEach(() => dirA = null);
class DirA { class DirA {
constructor(@Host() public dirB: DirB) {} constructor(@Host() public dirB: DirB) {}
@ -1023,28 +1025,106 @@ describe('di', () => {
}); });
} }
/** <div dirA></div> */ it('should not find providers across component boundaries', () => {
const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) { /** <div dirA></div> */
if (rf & RenderFlags.Create) { const Comp = createComponent('comp', function(rf: RenderFlags, ctx: any) {
element(0, 'div', ['dirA', '']); if (rf & RenderFlags.Create) {
element(0, 'div', ['dirA', '']);
}
}, 1, 0, [DirA, DirB]);
/* <comp dirB></comp> */
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) {
* <div dirA></div>
* % }
*/
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]);
/* <comp dirB></comp> */
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]);
/* <comp dirB></comp> */ /**
const App = createComponent('app', function(rf: RenderFlags, ctx: any) { * <div dirB>
if (rf & RenderFlags.Create) { * <div *ngIf="showing" dirA></div>
element(0, 'comp', ['dirB', '']); * </div>
} */
}, 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(() => { // testing only
new ComponentFixture(App); dirB = getDirectiveOnNode(0);
}).toThrowError(/NodeInjector: NOT_FOUND \[DirB\]/); }
}, 2, 1, [NgIf, DirA, DirB]);
const fixture = new ComponentFixture(App);
fixture.component.showing = true;
fixture.update();
expect(dirA !.dirB).toEqual(dirB);
});
}); });
}); });
}); });
describe('Special tokens', () => { describe('Special tokens', () => {