@@ -13,7 +13,7 @@ import {
1313 Utils ,
1414 View ,
1515} from '@nativescript/core' ;
16- import { Observable , Subject } from 'rxjs' ;
16+ import { Observable , ReplaySubject , Subject } from 'rxjs' ;
1717import { filter , map , take } from 'rxjs/operators' ;
1818import { AppHostView } from './app-host-view' ;
1919import {
@@ -22,6 +22,8 @@ import {
2222 resetAngularHmrCompiledComponents ,
2323 setAngularCoreForHmr ,
2424} from './hmr-compiled-components-core' ;
25+ import { _hmrDiagBumpCycle , installAngularHmrComponentRegistrar } from './hmr-class-registry' ;
26+ import { installHmrEagerRegistrar , runHmrEagerInstantiators } from './hmr-eager-services' ;
2527import { NativeScriptLoadingService } from './loading.service' ;
2628import { clearAngularHmrRouteConfigCaches } from './legacy/router/hmr-route-cache-core' ;
2729import { NSLocationStrategy } from './legacy/router/ns-location-strategy' ;
@@ -35,8 +37,30 @@ import { NativeScriptDebug } from './trace';
3537// We need to use the original one that has the registered LViews
3638rememberAngularCoreForHmr ( AngularCore as any , globalThis as any ) ;
3739
40+ // Install the cross-module HMR component registrar. The Vite plugin
41+ // `ns-component-hmr-register` injects a call to the global hook
42+ // `__NS_HMR_REGISTER_COMPONENT__` at the end of every user `.ts` file
43+ // that declares an `@Component`-decorated class. After an HMR reboot,
44+ // each re-evaluated module pushes its fresh class into the registry,
45+ // and HMR helpers (modal restore, route replay) read the registry via
46+ // `getFreshComponentClass` to re-attach to the *live* class instead of
47+ // a captured stale reference. Production short-circuits inside the
48+ // helper (the hook is never assigned).
49+ //
50+ // We install the registrar before any other module initialization can
51+ // reference the hook so a user module loaded synchronously alongside
52+ // `@nativescript/angular` always finds the function present.
53+ installAngularHmrComponentRegistrar ( ) ;
54+
55+ // Install the cross-module registration entry point used by HMR-aware
56+ // services (e.g. `NativeDialog`) to ask for eager construction after
57+ // every bootstrap. Idempotent: re-evaluations after HMR are no-ops.
58+ installHmrEagerRegistrar ( ) ;
59+
3860const angularHmrGlobal = globalThis as any ;
39- angularHmrGlobal . __NS_REMEMBER_ANGULAR_CORE__ = ( core : any ) => setAngularCoreForHmr ( core , angularHmrGlobal ) ;
61+ angularHmrGlobal . __NS_REMEMBER_ANGULAR_CORE__ = ( core : any ) => {
62+ setAngularCoreForHmr ( core , angularHmrGlobal ) ;
63+ } ;
4064
4165export interface AppLaunchView extends LayoutBase {
4266 // called when the animation is to begin
@@ -68,7 +92,17 @@ export type NgModuleEvent =
6892 } ;
6993
7094export const preAngularDisposal$ = new Subject < NgModuleEvent > ( ) ;
71- export const postAngularBootstrap$ = new Subject < NgModuleEvent > ( ) ;
95+ /**
96+ * Stream that emits when an Angular module finishes bootstrapping. Modeled
97+ * as a `ReplaySubject(1)` so consumers (e.g. `NativeDialog`) instantiated
98+ * lazily — *after* the bootstrap event has already fired — still receive
99+ * the latest event and can react. Without buffering, a service that the
100+ * user app injects on first need (after bootstrap) would silently miss the
101+ * `hotreload` notification and skip HMR-only work like restoring captured
102+ * modal state. The buffer size of 1 means each new HMR cycle replaces the
103+ * cached event so cycle N's late subscribers don't see cycle N-1's event.
104+ */
105+ export const postAngularBootstrap$ = new ReplaySubject < NgModuleEvent > ( 1 ) ;
72106
73107/**
74108 * @deprecated
@@ -127,17 +161,33 @@ function emitModuleBootstrapEvent<T>(
127161 name : 'main' | 'loading' ,
128162 reason : NgModuleReason ,
129163) {
164+ console . info ( `[ns-hmr-diag][application] emitModuleBootstrapEvent name=${ name } reason=${ reason } ` ) ;
165+ // Instantiate registered HMR-aware services *before* emitting so they
166+ // attach their subscriptions in the same JS task and are guaranteed to
167+ // observe the event being emitted. `postAngularBootstrap$` is also a
168+ // `ReplaySubject(1)`, so a service injected later still receives the
169+ // buffered event — the eager pass is the fast path that lets the
170+ // restore work begin in the same task as bootstrap completion.
171+ if ( name === 'main' ) {
172+ runHmrEagerInstantiators (
173+ ( ref as ApplicationRef | NgModuleRef < T > ) . injector ,
174+ ( err ) => NativeScriptDebug . bootstrapLogError ( `HMR eager instantiator threw: ${ ( err as Error ) ?. message ?? err } ` ) ,
175+ ) ;
176+ }
130177 postAngularBootstrap$ . next ( {
131178 moduleType : name ,
132179 reference : ref ,
133180 reason,
134181 } ) ;
182+ console . info ( `[ns-hmr-diag][application] postAngularBootstrap$.next() emitted name=${ name } reason=${ reason } ` ) ;
135183}
136184
137185function destroyRef < T > ( ref : NgModuleRef < T > | ApplicationRef , name : 'main' | 'loading' , reason : NgModuleReason ) : void ;
138186function destroyRef ( ref : PlatformRef , reason : NgModuleReason ) : void ;
139187function destroyRef < T > ( ref : PlatformRef | ApplicationRef | NgModuleRef < T > , name ?: string , reason ?: string ) : void {
140188 if ( ref ) {
189+ const refKind = ref instanceof PlatformRef ? 'PlatformRef' : ref instanceof NgModuleRef ? 'NgModuleRef' : ref instanceof ApplicationRef ? 'ApplicationRef' : '(unknown)' ;
190+ console . info ( `[ns-hmr-diag][application] destroyRef kind=${ refKind } name=${ name ?? '(none)' } reason=${ reason ?? '(none)' } ` ) ;
141191 if ( ref instanceof PlatformRef ) {
142192 preAngularDisposal$ . next ( {
143193 moduleType : 'platform' ,
@@ -153,6 +203,7 @@ function destroyRef<T>(ref: PlatformRef | ApplicationRef | NgModuleRef<T>, name?
153203 } ) ;
154204 }
155205 ref . destroy ( ) ;
206+ console . info ( `[ns-hmr-diag][application] destroyRef DONE kind=${ refKind } name=${ name ?? '(none)' } ` ) ;
156207 }
157208}
158209
@@ -690,6 +741,11 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
690741 disposePlatform ( 'hotreload' ) ;
691742 } ;
692743 global [ '__reboot_ng_modules__' ] = ( shouldDisposePlatform : boolean = false ) => {
744+ // Diagnostic: bump the global HMR cycle counter so all subsequent
745+ // log lines (class registry, dialog services) can be cross-
746+ // referenced to a specific reboot.
747+ const cycleNum = _hmrDiagBumpCycle ( ) ;
748+ console . info ( `[ns-hmr-diag][application] __reboot_ng_modules__ called cycle=${ cycleNum } shouldDisposePlatform=${ shouldDisposePlatform } bootstrapId=${ bootstrapId } hasMainModuleRef=${ ! ! mainModuleRef } ` ) ;
693749 const traceEnabled = NativeScriptDebug . isLogEnabled ( ) ;
694750 if ( traceEnabled ) {
695751 NativeScriptDebug . hmrLog (
@@ -700,6 +756,7 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
700756 global [ '__NS_CAPTURE_ANGULAR_HMR_ROUTE__' ] ?.( ) ;
701757 } catch { }
702758 disposeLastModules ( 'hotreload' ) ;
759+ console . info ( `[ns-hmr-diag][application] after disposeLastModules cycle=${ cycleNum } bootstrapId=${ bootstrapId } ` ) ;
703760 if ( traceEnabled ) {
704761 NativeScriptDebug . hmrLog ( `after disposeLastModules bootstrapId=${ bootstrapId } ` ) ;
705762 }
@@ -709,7 +766,9 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
709766 if ( traceEnabled ) {
710767 NativeScriptDebug . hmrLog ( 'calling bootstrapRoot' ) ;
711768 }
769+ console . info ( `[ns-hmr-diag][application] calling bootstrapRoot cycle=${ cycleNum } ` ) ;
712770 bootstrapRoot ( 'hotreload' ) ;
771+ console . info ( `[ns-hmr-diag][application] bootstrapRoot returned cycle=${ cycleNum } bootstrapId=${ bootstrapId } ` ) ;
713772 if ( traceEnabled ) {
714773 NativeScriptDebug . hmrLog ( `bootstrapRoot returned bootstrapId=${ bootstrapId } ` ) ;
715774 }
0 commit comments