Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

162 changes: 137 additions & 25 deletions packages/angular/src/lib/application.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as AngularCore from '@angular/core';
import { ApplicationRef, EnvironmentProviders, NgModuleRef, NgZone, PlatformRef, Provider } from '@angular/core';
import {
Application,
Expand All @@ -18,6 +19,11 @@ import { NativeScriptLoadingService } from './loading.service';
import { APP_ROOT_VIEW, DISABLE_ROOT_VIEW_HANDLING, NATIVESCRIPT_ROOT_MODULE_ID } from './tokens';
import { NativeScriptDebug } from './trace';

// Store the original @angular/core module for HMR
// This is crucial because HMR imports a fresh @angular/core with empty LView tracking
// We need to use the original one that has the registered LViews
(globalThis as any).__NS_ANGULAR_CORE__ = AngularCore;

export interface AppLaunchView extends LayoutBase {
// called when the animation is to begin
startAnimation?: () => void;
Expand Down Expand Up @@ -236,23 +242,28 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
let launchEventDone = true;
let targetRootView: View = null;
const setRootView = (ref: NgModuleRef<T | K> | ApplicationRef | View) => {
console.log('[ng-hmr] setRootView called, bootstrapId:', bootstrapId, 'ref type:', ref?.constructor?.name);
if (bootstrapId === -1) {
// treat edge cases
console.log('[ng-hmr] setRootView: bootstrapId is -1, returning early');
return;
}
if (ref instanceof NgModuleRef || ref instanceof ApplicationRef) {
if (ref.injector.get(DISABLE_ROOT_VIEW_HANDLING, false)) {
console.log('[ng-hmr] setRootView: DISABLE_ROOT_VIEW_HANDLING is true, returning');
return;
}
} else {
if (ref['__disable_root_view_handling']) {
console.log('[ng-hmr] setRootView: __disable_root_view_handling is true, returning');
return;
}
}
Application.getRootView()?._closeAllModalViewsInternal(); // cleanup old rootview
NativeScriptDebug.bootstrapLog(`Setting RootView ${launchEventDone ? 'outside of' : 'during'} launch event`);
// TODO: check for leaks when root view isn't properly destroyed
if (ref instanceof View) {
console.log('[ng-hmr] setRootView: ref is View, launchEventDone:', launchEventDone);
if (NativeScriptDebug.isLogEnabled()) {
NativeScriptDebug.bootstrapLog(`Setting RootView to ${ref}`);
}
Expand All @@ -267,14 +278,35 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
}
const view = ref.injector.get(APP_ROOT_VIEW) as AppHostView | View;
const newRoot = view instanceof AppHostView ? view.content : view;
console.log('[ng-hmr] setRootView: view from injector:', view?.constructor?.name, 'newRoot:', newRoot?.constructor?.name);
console.log('[ng-hmr] setRootView: launchEventDone:', launchEventDone, 'embedded:', options.embedded);
if (NativeScriptDebug.isLogEnabled()) {
NativeScriptDebug.bootstrapLog(`Setting RootView to ${newRoot}`);
}
if (options.embedded) {
console.log('[ng-hmr] setRootView: calling Application.run (embedded)');
Application.run({ create: () => newRoot });
} else if (launchEventDone) {
console.log('[ng-hmr] setRootView: calling Application.resetRootView');
console.log('[ng-hmr] setRootView: newRoot details:', {
type: newRoot?.constructor?.name,
nativeView: !!newRoot?.nativeView,
parent: newRoot?.parent?.constructor?.name,
childCount: (newRoot as any)?.getChildrenCount?.() ?? 'N/A',
});
Application.resetRootView({ create: () => newRoot });
console.log('[ng-hmr] setRootView: Application.resetRootView returned');
// Check root view after reset
setTimeout(() => {
const currentRoot = Application.getRootView();
console.log('[ng-hmr] setRootView: after reset, getRootView:', {
type: currentRoot?.constructor?.name,
nativeView: !!currentRoot?.nativeView,
childCount: (currentRoot as any)?.getChildrenCount?.() ?? 'N/A',
});
}, 100);
} else {
console.log('[ng-hmr] setRootView: setting targetRootView (launch in progress)');
targetRootView = newRoot;
}
};
Expand All @@ -286,8 +318,10 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
setRootView(errorTextBox);
};
const bootstrapRoot = (reason: NgModuleReason) => {
console.log('[ng-hmr] bootstrapRoot called, reason:', reason);
try {
bootstrapId = Date.now();
console.log('[ng-hmr] bootstrapRoot: new bootstrapId:', bootstrapId);
const currentBootstrapId = bootstrapId;
let bootstrapped = false;
let onMainBootstrap = () => {
Expand All @@ -297,20 +331,70 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
() =>
options.appModuleBootstrap(reason).then(
(ref) => {
console.log('[ng-hmr] appModuleBootstrap resolved, ref:', ref?.constructor?.name);
console.log('[ng-hmr] currentBootstrapId:', currentBootstrapId, 'bootstrapId:', bootstrapId);
if (currentBootstrapId !== bootstrapId) {
// this module is old and not needed anymore
// this may happen when developer uses async app initializer and the user exits the app before this bootstraps
console.log('[ng-hmr] bootstrap ID mismatch, destroying ref');
ref.destroy();
return;
}
mainModuleRef = ref;

// Expose ApplicationRef for HMR to trigger change detection
// Check for ApplicationRef by duck-typing since instanceof can fail across module realms
const refAny = ref as any;
const isAppRef = refAny && typeof refAny.tick === 'function' && Array.isArray(refAny.components);
console.log('[ng-hmr] ref type check: isAppRef=', isAppRef, 'has tick=', typeof refAny?.tick === 'function', 'has components=', Array.isArray(refAny?.components));

if (isAppRef) {
global['__NS_ANGULAR_APP_REF__'] = ref;
// Mark boot complete for the HMR system
global['__NS_HMR_BOOT_COMPLETE__'] = true;

// Register bootstrapped components for HMR lookup
if (!global['__NS_ANGULAR_COMPONENTS__']) {
global['__NS_ANGULAR_COMPONENTS__'] = {};
}
// Get the component class from the first bootstrapped component
console.log('[ng-hmr] ApplicationRef components count:', refAny.components?.length);
if (refAny.components && refAny.components.length > 0) {
const componentRef = refAny.components[0];
console.log('[ng-hmr] componentRef:', componentRef?.constructor?.name);
console.log('[ng-hmr] componentRef.componentType:', componentRef?.componentType?.name);

// For Angular 17+ standalone components, the component type is on componentRef.componentType
// For older Angular, try componentRef.instance.constructor
let componentType = componentRef?.componentType;
if (!componentType && componentRef?.instance) {
componentType = componentRef.instance.constructor;
}

if (componentType && componentType.name) {
global['__NS_ANGULAR_COMPONENTS__'][componentType.name] = componentType;
console.log('[ng-hmr] Registered component for HMR:', componentType.name);
} else {
console.log('[ng-hmr] Could not get componentType name');
}
} else {
console.log('[ng-hmr] No components in ApplicationRef');
}
} else {
const appRef = ref.injector.get(ApplicationRef, null);
if (appRef) {
global['__NS_ANGULAR_APP_REF__'] = appRef;
// Mark boot complete for the HMR system
global['__NS_HMR_BOOT_COMPLETE__'] = true;
}
}

(ref instanceof ApplicationRef ? ref.components[0] : ref).onDestroy(
(isAppRef ? refAny.components[0] : ref).onDestroy(
() => (mainModuleRef = mainModuleRef === ref ? null : mainModuleRef),
);
updatePlatformRef(ref, reason);
const styleTag = ref.injector.get(NATIVESCRIPT_ROOT_MODULE_ID);
(ref instanceof ApplicationRef ? ref.components[0] : ref).onDestroy(() => {
(isAppRef ? refAny.components[0] : ref).onDestroy(() => {
removeTaggedAdditionalCSS(styleTag);
});
bootstrapped = true;
Expand Down Expand Up @@ -457,30 +541,58 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
if (oldAddEventListener) {
global.NativeScriptGlobals.events.addEventListener = oldAddEventListener;
}
if (import.meta['webpackHot']) {
// handle HMR Application.run
global['__dispose_app_ng_platform__'] = () => {
disposePlatform('hotreload');
};
global['__dispose_app_ng_modules__'] = () => {
disposeLastModules('hotreload');
};
global['__bootstrap_app_ng_modules__'] = () => {
bootstrapRoot('hotreload');
};
global['__cleanup_ng_hot__'] = () => {
Application.off(Application.launchEvent, launchCallback);
Application.off(Application.exitEvent, exitCallback);
disposeLastModules('hotreload');

// Detect HMR environment (webpack or Vite)
const isWebpackHot = !!import.meta['webpackHot'];
const isViteHot = !!import.meta['hot'];
const isHotReloadEnabled = isWebpackHot || isViteHot;

// Always expose HMR globals for both webpack and Vite HMR support
// These allow the HMR runtime to properly dispose and re-bootstrap Angular
global['__dispose_app_ng_platform__'] = () => {
disposePlatform('hotreload');
};
global['__dispose_app_ng_modules__'] = () => {
disposeLastModules('hotreload');
};
global['__bootstrap_app_ng_modules__'] = () => {
bootstrapRoot('hotreload');
};
global['__cleanup_ng_hot__'] = () => {
Application.off(Application.launchEvent, launchCallback);
Application.off(Application.exitEvent, exitCallback);
disposeLastModules('hotreload');
disposePlatform('hotreload');
};
global['__reboot_ng_modules__'] = (shouldDisposePlatform: boolean = false) => {
console.log('[ng-hmr] __reboot_ng_modules__ called, shouldDisposePlatform:', shouldDisposePlatform);
console.log('[ng-hmr] current bootstrapId:', bootstrapId, 'mainModuleRef:', !!mainModuleRef);
disposeLastModules('hotreload');
console.log('[ng-hmr] after disposeLastModules, bootstrapId:', bootstrapId);
if (shouldDisposePlatform) {
disposePlatform('hotreload');
};
global['__reboot_ng_modules__'] = (shouldDisposePlatform: boolean = false) => {
disposeLastModules('hotreload');
if (shouldDisposePlatform) {
disposePlatform('hotreload');
}
bootstrapRoot('hotreload');
};
}
console.log('[ng-hmr] calling bootstrapRoot...');
bootstrapRoot('hotreload');
console.log('[ng-hmr] bootstrapRoot returned, new bootstrapId:', bootstrapId);
};

if (isWebpackHot) {
// Webpack-specific HMR handling
import.meta['webpackHot'].decline();

if (!Application.hasLaunched()) {
Application.run();
return;
}
bootstrapRoot('hotreload');
return;
}

if (isViteHot) {
// Vite-specific HMR handling
// Vite HMR is handled by @nativescript/vite's HMR runtime
// which will call __reboot_ng_modules__ when needed

if (!Application.hasLaunched()) {
Application.run();
Expand Down
15 changes: 0 additions & 15 deletions packages/zone-js/dist/connectivity.ts

This file was deleted.

59 changes: 0 additions & 59 deletions packages/zone-js/dist/core.ts

This file was deleted.

Loading
Loading