Skip to content

Commit 0879d2e

Browse files
committed
refactor(core): throw more descriptive error message in case of invalid host element (angular#35916)
This commit replaces an assert with more descriptive error message that is thrown in case `<ng-template>` or `<ng-container>` is used as host element for a Component. Resolves angular#35240. PR Close angular#35916
1 parent 83fe963 commit 0879d2e

File tree

12 files changed

+95
-28
lines changed

12 files changed

+95
-28
lines changed

goldens/size-tracking/integration-payloads.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.",
6363
"bundle": "TODO(i): (FW-2164) TS 3.9 new class shape seems to have broken Closure in big ways. The size went from 169991 to 252338",
6464
"bundle": "TODO(i): after removal of tsickle from ngc-wrapped / ng_package, we had to switch to SIMPLE optimizations which increased the size from 252338 to 1198917, see PR#37221 and PR#37317 for more info",
65-
"bundle": 1209688
65+
"bundle": 1210239
6666
}
6767
}
6868
}

packages/core/src/render3/component_ref.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
225225
createElementRef(viewEngine_ElementRef, tElementNode, rootLView), rootLView, tElementNode);
226226

227227
// The host element of the internal root view is attached to the component's host view node.
228-
ngDevMode && assertNodeOfPossibleTypes(rootTView.node, TNodeType.View);
228+
ngDevMode && assertNodeOfPossibleTypes(rootTView.node, [TNodeType.View]);
229229
rootTView.node!.child = tElementNode;
230230

231231
return componentRef;

packages/core/src/render3/di.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ export function diPublicInInjector(
267267
export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): string|null {
268268
ngDevMode &&
269269
assertNodeOfPossibleTypes(
270-
tNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer);
270+
tNode, [TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer]);
271271
ngDevMode && assertDefined(tNode, 'expecting tNode');
272272
if (attrNameToInject === 'class') {
273273
return tNode.classes;

packages/core/src/render3/instructions/di.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import {InjectFlags, InjectionToken, resolveForwardRef} from '../../di';
99
import {ɵɵinject} from '../../di/injector_compatibility';
1010
import {Type} from '../../interface/type';
1111
import {getOrCreateInjectable, injectAttributeImpl} from '../di';
12-
import {TDirectiveHostNode, TNodeType} from '../interfaces/node';
13-
import {assertNodeOfPossibleTypes} from '../node_assert';
12+
import {TDirectiveHostNode} from '../interfaces/node';
1413
import {getLView, getPreviousOrParentTNode} from '../state';
1514

1615
/**

packages/core/src/render3/instructions/listener.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ function listenerInternal(
128128

129129
ngDevMode &&
130130
assertNodeOfPossibleTypes(
131-
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
131+
tNode, [TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer]);
132132

133133
let processOutputs = true;
134134

packages/core/src/render3/instructions/shared.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGrea
1515
import {createNamedArrayType} from '../../util/named_array_type';
1616
import {initNgDevMode} from '../../util/ng_dev_mode';
1717
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
18+
import {stringify} from '../../util/stringify';
1819
import {assertFirstCreatePass, assertLContainer, assertLView} from '../assert';
1920
import {attachPatchData} from '../context_discovery';
2021
import {getFactoryDef} from '../definition';
@@ -272,7 +273,7 @@ export function assignTViewNodeToLView(
272273
let tNode = tView.node;
273274
if (tNode == null) {
274275
ngDevMode && tParentNode &&
275-
assertNodeOfPossibleTypes(tParentNode, TNodeType.Element, TNodeType.Container);
276+
assertNodeOfPossibleTypes(tParentNode, [TNodeType.Element, TNodeType.Container]);
276277
tView.node = tNode = createTNode(
277278
tView,
278279
tParentNode as TElementNode | TContainerNode | null, //
@@ -1278,7 +1279,7 @@ function instantiateAllDirectives(
12781279
const isComponent = isComponentDef(def);
12791280

12801281
if (isComponent) {
1281-
ngDevMode && assertNodeOfPossibleTypes(tNode, TNodeType.Element);
1282+
ngDevMode && assertNodeOfPossibleTypes(tNode, [TNodeType.Element]);
12821283
addComponentLogic(lView, tNode as TElementNode, def as ComponentDef<any>);
12831284
}
12841285

@@ -1366,7 +1367,7 @@ function findDirectiveDefMatches(
13661367
ngDevMode && assertFirstCreatePass(tView);
13671368
ngDevMode &&
13681369
assertNodeOfPossibleTypes(
1369-
tNode, TNodeType.Element, TNodeType.ElementContainer, TNodeType.Container);
1370+
tNode, [TNodeType.Element, TNodeType.ElementContainer, TNodeType.Container]);
13701371
const registry = tView.directiveRegistry;
13711372
let matches: any[]|null = null;
13721373
if (registry) {
@@ -1377,6 +1378,12 @@ function findDirectiveDefMatches(
13771378
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, viewData), tView, def.type);
13781379

13791380
if (isComponentDef(def)) {
1381+
ngDevMode &&
1382+
assertNodeOfPossibleTypes(
1383+
tNode, [TNodeType.Element],
1384+
`"${tNode.tagName}" tags cannot be used as component hosts. ` +
1385+
`Please use a different tag to activate the ${
1386+
stringify(def.type)} component.`);
13801387
if (tNode.flags & TNodeFlags.isComponentHost) throwMultipleComponentError(tNode);
13811388
markAsComponentHost(tView, tNode);
13821389
// The component is always stored first with directives after.

packages/core/src/render3/node_assert.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ export function assertNodeType(tNode: TNode, type: TNodeType): asserts tNode is
2626
assertEqual(tNode.type, type, `should be a ${typeName(type)}`);
2727
}
2828

29-
export function assertNodeOfPossibleTypes(tNode: TNode|null, ...types: TNodeType[]): void {
29+
export function assertNodeOfPossibleTypes(
30+
tNode: TNode|null, types: TNodeType[], message?: string): void {
3031
assertDefined(tNode, 'should be called with a TNode');
3132
const found = types.some(type => tNode.type === type);
3233
assertEqual(
3334
found, true,
34-
`Should be one of ${types.map(typeName).join(', ')} but got ${typeName(tNode.type)}`);
35+
message ??
36+
`Should be one of ${types.map(typeName).join(', ')} but got ${typeName(tNode.type)}`);
3537
}
3638

3739
export function assertNodeNotOfTypes(tNode: TNode, types: TNodeType[], message?: string): void {

packages/core/src/render3/node_manipulation.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ function getRenderParent(tView: TView, tNode: TNode, currentView: LView): REleme
552552
} else {
553553
// We are inserting a root element of the component view into the component host element and
554554
// it should always be eager.
555-
ngDevMode && assertNodeOfPossibleTypes(hostTNode, TNodeType.Element);
555+
ngDevMode && assertNodeOfPossibleTypes(hostTNode, [TNodeType.Element]);
556556
return currentView[HOST];
557557
}
558558
} else {
@@ -698,10 +698,10 @@ export function appendChild(
698698
*/
699699
function getFirstNativeNode(lView: LView, tNode: TNode|null): RNode|null {
700700
if (tNode !== null) {
701-
ngDevMode &&
702-
assertNodeOfPossibleTypes(
703-
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer,
704-
TNodeType.IcuContainer, TNodeType.Projection);
701+
ngDevMode && assertNodeOfPossibleTypes(tNode, [
702+
TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer, TNodeType.IcuContainer,
703+
TNodeType.Projection
704+
]);
705705

706706
const tNodeType = tNode.type;
707707
if (tNodeType === TNodeType.Element) {
@@ -778,10 +778,10 @@ function applyNodes(
778778
renderParent: RElement|null, beforeNode: RNode|null, isProjection: boolean) {
779779
while (tNode != null) {
780780
ngDevMode && assertTNodeForLView(tNode, lView);
781-
ngDevMode &&
782-
assertNodeOfPossibleTypes(
783-
tNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer,
784-
TNodeType.Projection, TNodeType.Projection, TNodeType.IcuContainer);
781+
ngDevMode && assertNodeOfPossibleTypes(tNode, [
782+
TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer, TNodeType.Projection,
783+
TNodeType.IcuContainer
784+
]);
785785
const rawSlotValue = lView[tNode.index];
786786
const tNodeType = tNode.type;
787787
if (isProjection) {
@@ -798,7 +798,7 @@ function applyNodes(
798798
applyProjectionRecursive(
799799
renderer, action, lView, tNode as TProjectionNode, renderParent, beforeNode);
800800
} else {
801-
ngDevMode && assertNodeOfPossibleTypes(tNode, TNodeType.Element, TNodeType.Container);
801+
ngDevMode && assertNodeOfPossibleTypes(tNode, [TNodeType.Element, TNodeType.Container]);
802802
applyToElementOrContainer(action, renderer, renderParent, rawSlotValue, beforeNode);
803803
}
804804
}

packages/core/src/render3/query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ function createSpecialToken(lView: LView, tNode: TNode, read: any): any {
326326
} else if (read === ViewContainerRef) {
327327
ngDevMode &&
328328
assertNodeOfPossibleTypes(
329-
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
329+
tNode, [TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer]);
330330
return createContainerRef(
331331
ViewContainerRef, ViewEngine_ElementRef,
332332
tNode as TElementNode | TContainerNode | TElementContainerNode, lView);

packages/core/src/render3/view_engine_compatibility.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ export function createContainerRef(
340340

341341
ngDevMode &&
342342
assertNodeOfPossibleTypes(
343-
hostTNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer);
343+
hostTNode, [TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer]);
344344

345345
let lContainer: LContainer;
346346
const slotValue = hostView[hostTNode.index];

0 commit comments

Comments
 (0)