fix(ivy): injecting optional TemplateRef on element should not throw (#26664)

PR Close #26664
This commit is contained in:
Kara Erickson 2018-10-25 11:18:49 -07:00 committed by Matias Niemelä
parent d2e6d6978e
commit d52d82d744
4 changed files with 79 additions and 25 deletions

View File

@ -55,7 +55,7 @@ export abstract class TemplateRef<C> {
/** @internal */ /** @internal */
static __NG_ELEMENT_ID__: static __NG_ELEMENT_ID__:
() => TemplateRef<any> = () => SWITCH_TEMPLATE_REF_FACTORY(TemplateRef, ElementRef) () => TemplateRef<any>| null = () => SWITCH_TEMPLATE_REF_FACTORY(TemplateRef, ElementRef)
} }
export const SWITCH_TEMPLATE_REF_FACTORY__POST_R3__ = render3InjectTemplateRef; export const SWITCH_TEMPLATE_REF_FACTORY__POST_R3__ = render3InjectTemplateRef;

View File

@ -23,8 +23,6 @@ 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, getParentInjectorViewOffset, 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.
* *
@ -300,7 +298,12 @@ export function getOrCreateInjectable<T>(
const saveViewData = getViewData(); const saveViewData = getViewData();
setTNodeAndViewData(tNode, lViewData); setTNodeAndViewData(tNode, lViewData);
try { try {
return bloomHash(); const value = bloomHash();
if (value == null && !(flags & InjectFlags.Optional)) {
throw new Error(`No provider for ${stringify(token)}`);
} else {
return value;
}
} finally { } finally {
setTNodeAndViewData(savePreviousOrParentTNode, saveViewData); setTNodeAndViewData(savePreviousOrParentTNode, saveViewData);
} }

View File

@ -77,7 +77,7 @@ let R3TemplateRef: {
*/ */
export function injectTemplateRef<T>( export function injectTemplateRef<T>(
TemplateRefToken: typeof ViewEngine_TemplateRef, TemplateRefToken: typeof ViewEngine_TemplateRef,
ElementRefToken: typeof ViewEngine_ElementRef): ViewEngine_TemplateRef<T> { ElementRefToken: typeof ViewEngine_ElementRef): ViewEngine_TemplateRef<T>|null {
return createTemplateRef<T>( return createTemplateRef<T>(
TemplateRefToken, ElementRefToken, getPreviousOrParentTNode(), getViewData()); TemplateRefToken, ElementRefToken, getPreviousOrParentTNode(), getViewData());
} }
@ -93,7 +93,7 @@ export function injectTemplateRef<T>(
*/ */
export function createTemplateRef<T>( export function createTemplateRef<T>(
TemplateRefToken: typeof ViewEngine_TemplateRef, ElementRefToken: typeof ViewEngine_ElementRef, TemplateRefToken: typeof ViewEngine_TemplateRef, ElementRefToken: typeof ViewEngine_ElementRef,
hostTNode: TNode, hostView: LViewData): ViewEngine_TemplateRef<T> { hostTNode: TNode, hostView: LViewData): ViewEngine_TemplateRef<T>|null {
if (!R3TemplateRef) { if (!R3TemplateRef) {
// TODO: Fix class name, should be TemplateRef, but there appears to be a rollup bug // TODO: Fix class name, should be TemplateRef, but there appears to be a rollup bug
R3TemplateRef = class TemplateRef_<T> extends TemplateRefToken<T> { R3TemplateRef = class TemplateRef_<T> extends TemplateRefToken<T> {
@ -122,12 +122,15 @@ export function createTemplateRef<T>(
}; };
} }
const hostContainer: LContainer = hostView[hostTNode.index]; if (hostTNode.type === TNodeType.Container) {
ngDevMode && assertNodeType(hostTNode, TNodeType.Container); const hostContainer: LContainer = hostView[hostTNode.index];
ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated'); ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated');
return new R3TemplateRef( return new R3TemplateRef(
hostView, createElementRef(ElementRefToken, hostTNode, hostView), hostTNode.tViews as TView, hostView, createElementRef(ElementRefToken, hostTNode, hostView), hostTNode.tViews as TView,
getRenderer(), hostContainer[QUERIES], hostTNode.injectorIndex); getRenderer(), hostContainer[QUERIES], hostTNode.injectorIndex);
} else {
return null;
}
} }
let R3ViewContainerRef: { let R3ViewContainerRef: {

View File

@ -1181,21 +1181,20 @@ describe('di', () => {
}); });
describe('TemplateRef', () => { describe('TemplateRef', () => {
it('should create directive with TemplateRef dependencies', () => { class Directive {
value: string;
class Directive { constructor(public templateRef: TemplateRef<any>) {
value: string; this.value = (templateRef.constructor as any).name;
constructor(public templateRef: TemplateRef<any>) {
this.value = (templateRef.constructor as any).name;
}
static ngDirectiveDef = defineDirective({
type: Directive,
selectors: [['', 'dir', '']],
factory: () => new Directive(directiveInject(TemplateRef as any)),
exportAs: 'dir'
});
} }
static ngDirectiveDef = defineDirective({
type: Directive,
selectors: [['', 'dir', '']],
factory: () => new Directive(directiveInject(TemplateRef as any)),
exportAs: 'dir'
});
}
it('should create directive with TemplateRef dependencies', () => {
class DirectiveSameInstance { class DirectiveSameInstance {
isSameInstance: boolean; isSameInstance: boolean;
constructor(templateRef: TemplateRef<any>, directive: Directive) { constructor(templateRef: TemplateRef<any>, directive: Directive) {
@ -1233,6 +1232,55 @@ describe('di', () => {
expect(fixture.html).toContain('TemplateRef'); expect(fixture.html).toContain('TemplateRef');
expect(fixture.html).toContain('false'); expect(fixture.html).toContain('false');
}); });
it('should throw if injected on an element', () => {
/** <div dir></div> */
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dir', '']);
}
}, 1, 0, [Directive]);
expect(() => new ComponentFixture(App)).toThrowError(/No provider for TemplateRef/);
});
it('should throw if injected on an ng-container', () => {
/** <ng-container dir></ng-container> */
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
elementContainerStart(0, ['dir', '']);
elementContainerEnd();
}
}, 1, 0, [Directive]);
expect(() => new ComponentFixture(App)).toThrowError(/No provider for TemplateRef/);
});
it('should NOT throw if optional and injected on an element', () => {
let dir !: OptionalDirective;
class OptionalDirective {
constructor(@Optional() public templateRef: TemplateRef<any>) {}
static ngDirectiveDef = defineDirective({
type: OptionalDirective,
selectors: [['', 'dir', '']],
factory: () => dir = new OptionalDirective(
directiveInject(TemplateRef as any, InjectFlags.Optional)),
exportAs: 'dir'
});
}
/** <div dir></div> */
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
element(0, 'div', ['dir', '']);
}
}, 1, 0, [OptionalDirective]);
expect(() => new ComponentFixture(App)).not.toThrow();
expect(dir.templateRef).toBeNull();
});
}); });
describe('ViewContainerRef', () => { describe('ViewContainerRef', () => {