@nativescript/angular 21.0.1-alpha.7 → 21.0.1-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,6 +22,7 @@ class InvisibleNode extends View {
22
22
  this.name = name;
23
23
  this.nodeType = 1;
24
24
  this.nodeName = getClassName(this);
25
+ this.tagName = this.nodeName;
25
26
  }
26
27
  toString() {
27
28
  return `${this.nodeName}(${this.id})-${this.name}`;
@@ -2533,6 +2534,61 @@ function runNativeScriptAngularApp(options) {
2533
2534
  disposeLastModules('hotreload');
2534
2535
  disposePlatform('hotreload');
2535
2536
  };
2537
+ // Pre-import hook for HMR runtimes. Must be called BEFORE the changed
2538
+ // component modules are re-imported, otherwise their ɵɵdefineComponent
2539
+ // calls fire against the OLD `GENERATED_COMP_IDS` map and Angular emits
2540
+ // a benign-but-noisy NG0912 "Component ID generation collision" warning
2541
+ // for every component the user has touched. Calling
2542
+ // `ɵresetCompiledComponents` here clears the map (and the related
2543
+ // ownerNgModule / verifiedNgModule WeakMaps) so the fresh defs register
2544
+ // into an empty table.
2545
+ //
2546
+ // The post-reboot call inside `bootstrapRoot('hotreload')` remains in
2547
+ // place as a safety net: a project that doesn't wire its HMR runtime to
2548
+ // this hook still gets the reset (just one cycle late, after the warning
2549
+ // has already surfaced).
2550
+ global['__reset_ng_compiled_components__'] = () => {
2551
+ resetAngularHmrCompiledComponents(getAngularCoreForHmrReset(i0, globalThis));
2552
+ };
2553
+ // Suppress benign HMR-induced NG0912 ("Component ID generation collision")
2554
+ // warnings. On a `.ts` edit Angular Live Reload's
2555
+ // `ListenNowComponent_UpdateMetadata` function in the freshly re-imported
2556
+ // module calls `ɵɵreplaceMetadata` → `ɵɵdefineComponent` → `getComponentId`
2557
+ // against a class that shares its name with the just-rebooted instance but
2558
+ // not its identity (one comes from the route-loaded module, the other from
2559
+ // the dynamically-fetched `/@ng/component?c=…` metadata chunk). The check
2560
+ // surfaces every component the user touches as a noisy warning even though
2561
+ // there's no real collision — same logical class, two transient identities
2562
+ // during the HMR cycle.
2563
+ //
2564
+ // Real collisions — different classes that happen to hash to the same id —
2565
+ // produce a warning where `'X' and 'Y'` (different class names) appear in
2566
+ // the message. We only filter when both names match, so genuine duplicates
2567
+ // still reach the user.
2568
+ //
2569
+ // Install once per process; the filter self-detaches if the warning text
2570
+ // changes shape (defensive against future Angular wording tweaks).
2571
+ (() => {
2572
+ const w = global;
2573
+ if (w.__NS_ANGULAR_NG0912_FILTER_INSTALLED__)
2574
+ return;
2575
+ w.__NS_ANGULAR_NG0912_FILTER_INSTALLED__ = true;
2576
+ const origWarn = console.warn.bind(console);
2577
+ // Pattern: "Components 'Foo' and 'Bar' with selector 'xyz'" — capture
2578
+ // both class names and compare. We suppress only when they're identical
2579
+ // (the HMR pseudo-collision signature).
2580
+ const NG0912_NAME_MATCH = /NG0912[\s\S]*?Components '([^']+)' and '([^']+)' with selector/;
2581
+ console.warn = (...args) => {
2582
+ const msg = String(args[0] ?? '');
2583
+ if (msg.includes('NG0912')) {
2584
+ const m = NG0912_NAME_MATCH.exec(msg);
2585
+ if (m && m[1] === m[2]) {
2586
+ return;
2587
+ }
2588
+ }
2589
+ origWarn(...args);
2590
+ };
2591
+ })();
2536
2592
  global['__reboot_ng_modules__'] = (shouldDisposePlatform = false) => {
2537
2593
  // Bump the global HMR cycle counter so subsequent diagnostic log
2538
2594
  // lines (class registry, dialog services) can be cross-referenced
@@ -3144,6 +3200,12 @@ class ViewUtil {
3144
3200
  }
3145
3201
  const ngView = view;
3146
3202
  ngView.nodeName = name;
3203
+ // Angular 21+ reads `rootElement.tagName.toLowerCase()` during component bootstrap
3204
+ // (`locateHostElement`) to reject `<script>` host elements. Native Views have no
3205
+ // intrinsic `tagName`, so without this assignment the boot throws
3206
+ // `Cannot read properties of undefined (reading 'toLowerCase')`. Mirror DOM
3207
+ // conventions where `tagName` equals `nodeName` for element nodes.
3208
+ ngView.tagName = name;
3147
3209
  ngView.meta = getViewMeta(name);
3148
3210
  // we're setting the node type of the view
3149
3211
  // to 'element' because of checks done in the
@@ -3545,22 +3607,35 @@ class NativeScriptRenderer {
3545
3607
  if (NativeScriptDebug.enabled) {
3546
3608
  NativeScriptDebug.rendererLog(`NativeScriptRenderer.selectRootElement: ${selectorOrNode}`);
3547
3609
  }
3610
+ // Angular 21+ reads `rootElement.tagName.toLowerCase()` after this call
3611
+ // (`locateHostElement`) to reject `<script>` hosts. Guarantee every return
3612
+ // path produces a View with a non-empty string `tagName`; otherwise the
3613
+ // bootstrap throws `Cannot read properties of undefined (reading 'toLowerCase')`.
3614
+ const ensureTagName = (view, fallback) => {
3615
+ if (view && typeof view.tagName !== 'string') {
3616
+ try {
3617
+ view.tagName = view.nodeName || fallback || 'view';
3618
+ }
3619
+ catch { }
3620
+ }
3621
+ return view;
3622
+ };
3548
3623
  if (selectorOrNode instanceof View) {
3549
- return selectorOrNode;
3624
+ return ensureTagName(selectorOrNode, '');
3550
3625
  }
3551
3626
  if (selectorOrNode && selectorOrNode[0] === '#') {
3552
3627
  const result = getViewById(this.rootView, selectorOrNode.slice(1));
3553
- return (result || this.rootView);
3628
+ return ensureTagName((result || this.rootView), selectorOrNode);
3554
3629
  }
3555
3630
  if (typeof selectorOrNode === 'string') {
3556
3631
  const view = this.viewUtil.createView(selectorOrNode);
3557
3632
  if (getFirstNativeLikeView(view) === view) {
3558
3633
  // view is nativelike!
3559
3634
  this.appendChild(this.rootView, view);
3560
- return view;
3635
+ return ensureTagName(view, selectorOrNode);
3561
3636
  }
3562
3637
  }
3563
- return this.rootView;
3638
+ return ensureTagName(this.rootView, '');
3564
3639
  }
3565
3640
  parentNode(node) {
3566
3641
  if (NativeScriptDebug.enabled) {
@@ -9892,29 +9967,65 @@ function writeAngularHmrRouteState(value, options) {
9892
9967
  function captureAngularHmrPendingStartPath(value, source = 'hmr-reboot') {
9893
9968
  return writeAngularHmrRouteState(value, { pending: true, source });
9894
9969
  }
9970
+ /**
9971
+ * Match Angular Router's named-outlet syntax in a serialized URL.
9972
+ *
9973
+ * The router emits named outlets as `(outletName:segments[//otherName:segments])`
9974
+ * — see `DefaultUrlSerializer`. Any URL the captures match `/\(\w+:`
9975
+ * contains at least one named-outlet segment.
9976
+ *
9977
+ * Used to gate the start-path deferral below: when a captured URL has named
9978
+ * outlets it CANNOT be used as the initial-navigation path on the next boot
9979
+ * (the outlet directives are inside child components that don't exist yet at
9980
+ * `router.initialNavigation()` time, so `PageRouterOutlet.activateWith`
9981
+ * returns early with "No outlet found relative to activated route" and the
9982
+ * app renders a white screen).
9983
+ */
9984
+ function hasNamedOutletsInUrl(url) {
9985
+ if (typeof url !== 'string') {
9986
+ return false;
9987
+ }
9988
+ return /\([A-Za-z0-9_-]+:/.test(url);
9989
+ }
9895
9990
  function readAngularHmrPendingStartPath() {
9896
- // When a back-stack snapshot exists we boot to the bottom of the stack and
9897
- // let `replayAngularHmrPendingForwardNavigations` walk the rest. Otherwise
9898
- // fall back to the legacy single-URL slot so projects without history
9899
- // tracking still land on the page they were viewing.
9991
+ // HMR-DX policy: restore only the user's CURRENT URL. NativeScript Frames
9992
+ // own the back-stack, not the URL, so walking captured history URLs forward
9993
+ // doesn't reconstruct the page stack it just causes visible re-navigation
9994
+ // sequences (especially with tab-based named outlets) that the user has to
9995
+ // sit through after every save. We pick the last captured URL as the
9996
+ // restoration target and bail on the history walk entirely. The
9997
+ // `forward-navigations` reader returns at most one URL (the deferred
9998
+ // named-outlet case), so the replay service performs zero or one
9999
+ // post-bootstrap navigation, not N.
9900
10000
  const pendingHistory = readHistoryArray(PENDING_HISTORY_KEY);
9901
10001
  if (pendingHistory.length > 0) {
9902
- // Open the restoring-route window so user-app default navigations
9903
- // can step out of the framework's way until replay completes. The
9904
- // forward-navigation walk in `NativeScriptAngularHmrRouteReplay`
9905
- // closes the window after the final URL lands or fails. We pass
9906
- // the deepest captured URL so consumers can compare against the
9907
- // active router URL if they want fine-grained suppression.
9908
- beginAngularHmrRouteRestore(pendingHistory[pendingHistory.length - 1]);
9909
- return pendingHistory[0];
10002
+ const target = pendingHistory[pendingHistory.length - 1];
10003
+ beginAngularHmrRouteRestore(target);
10004
+ // Named-outlet URLs cannot be served as the initial navigation URL.
10005
+ // The outlet directives (e.g. `<page-router-outlet name="listenNowTab">`)
10006
+ // live inside child components that only render AFTER the primary
10007
+ // outlet activates. On `router.initialNavigation()` with a URL like
10008
+ // `/(listenNowTab:listen-now)`, Angular tries to activate `listenNowTab`
10009
+ // immediately and `PageRouterOutlet.activateWith` returns early because
10010
+ // no outlet is registered yet — white screen. Defer to a single forward
10011
+ // navigation that fires AFTER the first NavigationEnd, by which time the
10012
+ // outlet directives have registered.
10013
+ if (hasNamedOutletsInUrl(target)) {
10014
+ return '/';
10015
+ }
10016
+ return target;
9910
10017
  }
9911
10018
  const g = getGlobalState();
9912
10019
  const fallback = normalizeAngularHmrRouteUrl(g[PENDING_START_PATH_KEY]?.url ?? g[PENDING_START_PATH_KEY]) || '';
9913
10020
  if (fallback) {
9914
- // Single-URL fallback path: user-app code should still suppress
9915
- // default navigations briefly — the new bootstrap is about to
9916
- // navigate to `fallback`, so a default tab init that fires first
9917
- // would still stomp it.
10021
+ // Same deferral as above for the legacy single-URL slot.
10022
+ if (hasNamedOutletsInUrl(fallback)) {
10023
+ beginAngularHmrRouteRestore(fallback);
10024
+ // Stash the deferred URL so the forward-navigations reader picks it
10025
+ // up after the initial '/' navigation lands.
10026
+ writeHistoryArray(PENDING_HISTORY_KEY, [fallback]);
10027
+ return '/';
10028
+ }
9918
10029
  beginAngularHmrRouteRestore(fallback);
9919
10030
  }
9920
10031
  return fallback;
@@ -10067,15 +10178,29 @@ function readAngularHmrPendingRouteHistory() {
10067
10178
  }
10068
10179
  /**
10069
10180
  * Read URLs to navigate forward through after the initial navigation finishes.
10070
- * The first entry of the stack is the `START_PATH` consumed by the router; the
10071
- * rest are forward navigations to push onto the new back-stack.
10181
+ *
10182
+ * HMR-DX policy: at most one post-bootstrap navigation. We only need a forward
10183
+ * navigation when `readAngularHmrPendingStartPath` had to return '/' because
10184
+ * the user's current URL contains named outlets (the outlet directives don't
10185
+ * exist yet at initial-navigation time, so we defer to a single nav after the
10186
+ * primary outlet has registered them). For URLs with no named outlets the
10187
+ * start path IS the user's current URL and no forward step is needed.
10188
+ *
10189
+ * We intentionally do NOT walk the captured back-stack of intermediate URLs —
10190
+ * NativeScript Frames own the page stack, not the URL serializer, so URL
10191
+ * replay never reconstructs the Frame stack anyway and only creates visible
10192
+ * mid-save re-navigations.
10072
10193
  */
10073
10194
  function readAngularHmrPendingForwardNavigations() {
10074
10195
  const pending = readHistoryArray(PENDING_HISTORY_KEY);
10075
- if (pending.length <= 1) {
10196
+ if (pending.length === 0) {
10076
10197
  return [];
10077
10198
  }
10078
- return pending.slice(1);
10199
+ const target = pending[pending.length - 1];
10200
+ if (hasNamedOutletsInUrl(target)) {
10201
+ return [target];
10202
+ }
10203
+ return [];
10079
10204
  }
10080
10205
  /**
10081
10206
  * Clear the pending snapshot. The router replay calls this once it finishes
@@ -10169,14 +10294,18 @@ function endAngularHmrRouteRestore() {
10169
10294
  */
10170
10295
  const REPLAY_COMPLETED_GRACE_MS = 1000;
10171
10296
  /**
10172
- * Replays the back-stack snapshot captured by `NativeScriptAngularHmrRouteTracker`
10173
- * during HMR. The router's initial navigation already lands on the bottom of
10174
- * the stack (`stack[0]`); this service walks `stack[1..n]` so the user keeps
10175
- * back navigation across HMR cycles.
10297
+ * Restores the user's CURRENT URL after an HMR reboot.
10298
+ *
10299
+ * HMR-DX policy: at most one post-bootstrap navigation. The framework no
10300
+ * longer walks the captured back-stack (`stack[1..n]`) NativeScript Frames
10301
+ * own the page stack, not the URL serializer, so the URL walk never rebuilt
10302
+ * the Frame stack anyway, it only created visible mid-save re-navigation
10303
+ * sequences (especially with tab-based named outlets) that the user had to
10304
+ * sit through. Now `readAngularHmrPendingForwardNavigations()` returns at
10305
+ * most one URL — the deferred named-outlet case — and we replay only that.
10176
10306
  *
10177
- * The replay is single-shot per bootstrap. Any failure (cancelled navigation,
10178
- * unrouteable URL) aborts the rest of the replay so we don't fight the router
10179
- * — the user keeps whichever subset of the stack we successfully re-pushed.
10307
+ * Any failure (cancelled navigation, unrouteable URL) closes the restoring
10308
+ * window with `replay-aborted` so default navigations can resume.
10180
10309
  */
10181
10310
  class NativeScriptAngularHmrRouteReplay {
10182
10311
  constructor(router) {