@nativescript/angular 21.0.1-alpha.8 → 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.
@@ -2534,6 +2534,61 @@ function runNativeScriptAngularApp(options) {
2534
2534
  disposeLastModules('hotreload');
2535
2535
  disposePlatform('hotreload');
2536
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
+ })();
2537
2592
  global['__reboot_ng_modules__'] = (shouldDisposePlatform = false) => {
2538
2593
  // Bump the global HMR cycle counter so subsequent diagnostic log
2539
2594
  // lines (class registry, dialog services) can be cross-referenced
@@ -9912,29 +9967,65 @@ function writeAngularHmrRouteState(value, options) {
9912
9967
  function captureAngularHmrPendingStartPath(value, source = 'hmr-reboot') {
9913
9968
  return writeAngularHmrRouteState(value, { pending: true, source });
9914
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
+ }
9915
9990
  function readAngularHmrPendingStartPath() {
9916
- // When a back-stack snapshot exists we boot to the bottom of the stack and
9917
- // let `replayAngularHmrPendingForwardNavigations` walk the rest. Otherwise
9918
- // fall back to the legacy single-URL slot so projects without history
9919
- // 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.
9920
10000
  const pendingHistory = readHistoryArray(PENDING_HISTORY_KEY);
9921
10001
  if (pendingHistory.length > 0) {
9922
- // Open the restoring-route window so user-app default navigations
9923
- // can step out of the framework's way until replay completes. The
9924
- // forward-navigation walk in `NativeScriptAngularHmrRouteReplay`
9925
- // closes the window after the final URL lands or fails. We pass
9926
- // the deepest captured URL so consumers can compare against the
9927
- // active router URL if they want fine-grained suppression.
9928
- beginAngularHmrRouteRestore(pendingHistory[pendingHistory.length - 1]);
9929
- 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;
9930
10017
  }
9931
10018
  const g = getGlobalState();
9932
10019
  const fallback = normalizeAngularHmrRouteUrl(g[PENDING_START_PATH_KEY]?.url ?? g[PENDING_START_PATH_KEY]) || '';
9933
10020
  if (fallback) {
9934
- // Single-URL fallback path: user-app code should still suppress
9935
- // default navigations briefly — the new bootstrap is about to
9936
- // navigate to `fallback`, so a default tab init that fires first
9937
- // 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
+ }
9938
10029
  beginAngularHmrRouteRestore(fallback);
9939
10030
  }
9940
10031
  return fallback;
@@ -10087,15 +10178,29 @@ function readAngularHmrPendingRouteHistory() {
10087
10178
  }
10088
10179
  /**
10089
10180
  * Read URLs to navigate forward through after the initial navigation finishes.
10090
- * The first entry of the stack is the `START_PATH` consumed by the router; the
10091
- * 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.
10092
10193
  */
10093
10194
  function readAngularHmrPendingForwardNavigations() {
10094
10195
  const pending = readHistoryArray(PENDING_HISTORY_KEY);
10095
- if (pending.length <= 1) {
10196
+ if (pending.length === 0) {
10096
10197
  return [];
10097
10198
  }
10098
- return pending.slice(1);
10199
+ const target = pending[pending.length - 1];
10200
+ if (hasNamedOutletsInUrl(target)) {
10201
+ return [target];
10202
+ }
10203
+ return [];
10099
10204
  }
10100
10205
  /**
10101
10206
  * Clear the pending snapshot. The router replay calls this once it finishes
@@ -10189,14 +10294,18 @@ function endAngularHmrRouteRestore() {
10189
10294
  */
10190
10295
  const REPLAY_COMPLETED_GRACE_MS = 1000;
10191
10296
  /**
10192
- * Replays the back-stack snapshot captured by `NativeScriptAngularHmrRouteTracker`
10193
- * during HMR. The router's initial navigation already lands on the bottom of
10194
- * the stack (`stack[0]`); this service walks `stack[1..n]` so the user keeps
10195
- * 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.
10196
10306
  *
10197
- * The replay is single-shot per bootstrap. Any failure (cancelled navigation,
10198
- * unrouteable URL) aborts the rest of the replay so we don't fight the router
10199
- * — 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.
10200
10309
  */
10201
10310
  class NativeScriptAngularHmrRouteReplay {
10202
10311
  constructor(router) {