Skip to content

Commit 063d7ba

Browse files
authored
fix: set tagName on NgView for Angular 21 bootstrap compatibility (#168)
1 parent f8afa34 commit 063d7ba

4 files changed

Lines changed: 25 additions & 4 deletions

File tree

packages/angular/src/lib/nativescript-renderer.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,22 +274,34 @@ class NativeScriptRenderer implements Renderer2 {
274274
if (NativeScriptDebug.enabled) {
275275
NativeScriptDebug.rendererLog(`NativeScriptRenderer.selectRootElement: ${selectorOrNode}`);
276276
}
277+
// Angular 21+ reads `rootElement.tagName.toLowerCase()` after this call
278+
// (`locateHostElement`) to reject `<script>` hosts. Guarantee every return
279+
// path produces a View with a non-empty string `tagName`; otherwise the
280+
// bootstrap throws `Cannot read properties of undefined (reading 'toLowerCase')`.
281+
const ensureTagName = (view: any, fallback: string) => {
282+
if (view && typeof view.tagName !== 'string') {
283+
try {
284+
view.tagName = view.nodeName || fallback || 'view';
285+
} catch {}
286+
}
287+
return view;
288+
};
277289
if (selectorOrNode instanceof View) {
278-
return selectorOrNode;
290+
return ensureTagName(selectorOrNode, '');
279291
}
280292
if (selectorOrNode && selectorOrNode[0] === '#') {
281293
const result = getViewById(this.rootView, selectorOrNode.slice(1));
282-
return (result || this.rootView) as View;
294+
return ensureTagName((result || this.rootView) as View, selectorOrNode);
283295
}
284296
if (typeof selectorOrNode === 'string') {
285297
const view = this.viewUtil.createView(selectorOrNode);
286298
if (getFirstNativeLikeView(view) === view) {
287299
// view is nativelike!
288300
this.appendChild(this.rootView, view);
289-
return view;
301+
return ensureTagName(view, selectorOrNode);
290302
}
291303
}
292-
return this.rootView;
304+
return ensureTagName(this.rootView, '');
293305
}
294306
parentNode(node: NgView) {
295307
if (NativeScriptDebug.enabled) {

packages/angular/src/lib/view-util.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,12 @@ export class ViewUtil {
405405

406406
const ngView = view as NgView;
407407
ngView.nodeName = name;
408+
// Angular 21+ reads `rootElement.tagName.toLowerCase()` during component bootstrap
409+
// (`locateHostElement`) to reject `<script>` host elements. Native Views have no
410+
// intrinsic `tagName`, so without this assignment the boot throws
411+
// `Cannot read properties of undefined (reading 'toLowerCase')`. Mirror DOM
412+
// conventions where `tagName` equals `nodeName` for element nodes.
413+
ngView.tagName = name;
408414
ngView.meta = getViewMeta(name);
409415

410416
// we're setting the node type of the view

packages/angular/src/lib/views/invisible-nodes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export abstract class InvisibleNode extends View implements NgView {
77
meta: { skipAddToDom: boolean };
88
nodeType: number;
99
nodeName: string;
10+
tagName: string;
1011
// @ts-expect-error setter
1112
parentNode: NgView;
1213
nextSibling: NgView;
@@ -20,6 +21,7 @@ export abstract class InvisibleNode extends View implements NgView {
2021

2122
this.nodeType = 1;
2223
this.nodeName = getClassName(this);
24+
this.tagName = this.nodeName;
2325
}
2426

2527
toString() {

packages/angular/src/lib/views/view-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface ViewExtensions {
77
meta: ViewClassMeta;
88
nodeType: number;
99
nodeName: string;
10+
tagName: string;
1011
parentNode: NgView;
1112
nextSibling: NgView;
1213
previousSibling: NgView;

0 commit comments

Comments
 (0)