fix(ivy): injecting optional TemplateRef on element should not throw (#26664)
PR Close #26664
This commit is contained in:
parent
d2e6d6978e
commit
d52d82d744
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
Loading…
Reference in New Issue