refactor(core): de-duplicate bootstrap code between bootstrapApplication and bootstrapModule (#57060)

This commit de-duplicates the code for bootstrapping between
`bootstrapApplication` and `bootstrapModule`. A majority of the
bootstrap code was identical between the two, with some minor
differences that can be handled with a function overload.

PR Close #57060
This commit is contained in:
Andrew Scott
2024-07-19 15:50:01 -07:00
committed by Jessica Janiuk
parent 3459289ef0
commit 3da0254478
5 changed files with 194 additions and 168 deletions
@@ -6,34 +6,19 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Subscription} from 'rxjs';
import {
internalProvideZoneChangeDetection,
PROVIDED_NG_ZONE,
} from '../change_detection/scheduling/ng_zone_scheduling';
import {internalProvideZoneChangeDetection} from '../change_detection/scheduling/ng_zone_scheduling';
import {EnvironmentProviders, Provider, StaticProvider} from '../di/interface/provider';
import {EnvironmentInjector} from '../di/r3_injector';
import {ErrorHandler} from '../error_handler';
import {RuntimeError, RuntimeErrorCode} from '../errors';
import {DEFAULT_LOCALE_ID} from '../i18n/localization';
import {LOCALE_ID} from '../i18n/tokens';
import {ImagePerformanceWarning} from '../image_performance_warning';
import {Type} from '../interface/type';
import {createOrReusePlatformInjector} from '../platform/platform';
import {PLATFORM_DESTROY_LISTENERS} from '../platform/platform_ref';
import {assertStandaloneComponentType} from '../render3/errors';
import {setLocaleId} from '../render3/i18n/i18n_locale_id';
import {EnvironmentNgModuleRefAdapter} from '../render3/ng_module_ref';
import {NgZone} from '../zone/ng_zone';
import {ApplicationInitStatus} from './application_init';
import {_callAndReportToErrorHandler, ApplicationRef} from './application_ref';
import {
PROVIDED_ZONELESS,
ChangeDetectionScheduler,
} from '../change_detection/scheduling/zoneless_scheduling';
import {ChangeDetectionScheduler} from '../change_detection/scheduling/zoneless_scheduling';
import {ChangeDetectionSchedulerImpl} from '../change_detection/scheduling/zoneless_scheduling_impl';
import {bootstrap} from '../platform/bootstrap';
/**
* Internal create application API that implements the core application creation logic and optional
@@ -76,67 +61,11 @@ export function internalCreateApplication(config: {
// happens after we get the NgZone instance from the Injector.
runEnvironmentInitializers: false,
});
const envInjector = adapter.injector;
const ngZone = envInjector.get(NgZone);
return ngZone.run(() => {
envInjector.resolveInjectorInitializers();
const exceptionHandler: ErrorHandler | null = envInjector.get(ErrorHandler, null);
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (!exceptionHandler) {
throw new RuntimeError(
RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP,
'No `ErrorHandler` found in the Dependency Injection tree.',
);
}
if (envInjector.get(PROVIDED_ZONELESS) && envInjector.get(PROVIDED_NG_ZONE)) {
throw new RuntimeError(
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
'Invalid change detection configuration: ' +
'provideZoneChangeDetection and provideExperimentalZonelessChangeDetection cannot be used together.',
);
}
}
let onErrorSubscription: Subscription;
ngZone.runOutsideAngular(() => {
onErrorSubscription = ngZone.onError.subscribe({
next: (error: any) => {
exceptionHandler!.handleError(error);
},
});
});
// If the whole platform is destroyed, invoke the `destroy` method
// for all bootstrapped applications as well.
const destroyListener = () => envInjector.destroy();
const onPlatformDestroyListeners = platformInjector.get(PLATFORM_DESTROY_LISTENERS);
onPlatformDestroyListeners.add(destroyListener);
envInjector.onDestroy(() => {
onErrorSubscription.unsubscribe();
onPlatformDestroyListeners.delete(destroyListener);
});
return _callAndReportToErrorHandler(exceptionHandler!, ngZone, () => {
const initStatus = envInjector.get(ApplicationInitStatus);
initStatus.runInitializers();
return initStatus.donePromise.then(() => {
const localeId = envInjector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
setLocaleId(localeId || DEFAULT_LOCALE_ID);
const appRef = envInjector.get(ApplicationRef);
if (rootComponent !== undefined) {
appRef.bootstrap(rootComponent);
}
if (typeof ngDevMode === 'undefined' || ngDevMode) {
const imagePerformanceService = envInjector.get(ImagePerformanceWarning);
imagePerformanceService.start();
}
return appRef;
});
});
return bootstrap({
r3Injector: adapter.injector,
platformInjector,
rootComponent,
});
} catch (e) {
return Promise.reject(e);
+159
View File
@@ -0,0 +1,159 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Subscription} from 'rxjs';
import {PROVIDED_NG_ZONE} from '../change_detection/scheduling/ng_zone_scheduling';
import {EnvironmentInjector, R3Injector} from '../di/r3_injector';
import {ErrorHandler} from '../error_handler';
import {RuntimeError, RuntimeErrorCode} from '../errors';
import {DEFAULT_LOCALE_ID} from '../i18n/localization';
import {LOCALE_ID} from '../i18n/tokens';
import {ImagePerformanceWarning} from '../image_performance_warning';
import {Type} from '../interface/type';
import {PLATFORM_DESTROY_LISTENERS} from './platform_destroy_listeners';
import {setLocaleId} from '../render3/i18n/i18n_locale_id';
import {NgZone} from '../zone/ng_zone';
import {ApplicationInitStatus} from '../application/application_init';
import {_callAndReportToErrorHandler, ApplicationRef, remove} from '../application/application_ref';
import {PROVIDED_ZONELESS} from '../change_detection/scheduling/zoneless_scheduling';
import {Injector} from '../di';
import {InternalNgModuleRef, NgModuleRef} from '../linker/ng_module_factory';
import {stringify} from '../util/stringify';
export interface ModuleBootstrapConfig<M> {
moduleRef: InternalNgModuleRef<M>;
allPlatformModules: NgModuleRef<unknown>[];
}
export interface ApplicationBootstrapConfig {
r3Injector: R3Injector;
platformInjector: Injector;
rootComponent: Type<unknown> | undefined;
}
function isApplicationBootstrapConfig(
config: ApplicationBootstrapConfig | ModuleBootstrapConfig<unknown>,
): config is ApplicationBootstrapConfig {
return !!(config as ApplicationBootstrapConfig).platformInjector;
}
export function bootstrap<M>(
moduleBootstrapConfig: ModuleBootstrapConfig<M>,
): Promise<NgModuleRef<M>>;
export function bootstrap(
applicationBootstrapConfig: ApplicationBootstrapConfig,
): Promise<ApplicationRef>;
export function bootstrap<M>(
config: ModuleBootstrapConfig<M> | ApplicationBootstrapConfig,
): Promise<ApplicationRef> | Promise<NgModuleRef<M>> {
const envInjector = isApplicationBootstrapConfig(config)
? config.r3Injector
: config.moduleRef.injector;
const ngZone = envInjector.get(NgZone);
return ngZone.run(() => {
if (isApplicationBootstrapConfig(config)) {
config.r3Injector.resolveInjectorInitializers();
} else {
config.moduleRef.resolveInjectorInitializers();
}
const exceptionHandler = envInjector.get(ErrorHandler, null);
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (exceptionHandler === null) {
const errorMessage = isApplicationBootstrapConfig(config)
? 'No `ErrorHandler` found in the Dependency Injection tree.'
: 'No ErrorHandler. Is platform module (BrowserModule) included';
throw new RuntimeError(
RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP,
errorMessage,
);
}
if (envInjector.get(PROVIDED_ZONELESS) && envInjector.get(PROVIDED_NG_ZONE)) {
throw new RuntimeError(
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
'Invalid change detection configuration: ' +
'provideZoneChangeDetection and provideExperimentalZonelessChangeDetection cannot be used together.',
);
}
}
let onErrorSubscription: Subscription;
ngZone.runOutsideAngular(() => {
onErrorSubscription = ngZone.onError.subscribe({
next: (error: any) => {
exceptionHandler!.handleError(error);
},
});
});
if (isApplicationBootstrapConfig(config)) {
// If the whole platform is destroyed, invoke the `destroy` method
// for all bootstrapped applications as well.
const destroyListener = () => envInjector.destroy();
const onPlatformDestroyListeners = config.platformInjector.get(PLATFORM_DESTROY_LISTENERS);
onPlatformDestroyListeners.add(destroyListener);
envInjector.onDestroy(() => {
onErrorSubscription.unsubscribe();
onPlatformDestroyListeners.delete(destroyListener);
});
} else {
config.moduleRef.onDestroy(() => {
remove(config.allPlatformModules, config.moduleRef);
onErrorSubscription.unsubscribe();
});
}
return _callAndReportToErrorHandler(exceptionHandler!, ngZone, () => {
const initStatus = envInjector.get(ApplicationInitStatus);
initStatus.runInitializers();
return initStatus.donePromise.then(() => {
// If the `LOCALE_ID` provider is defined at bootstrap then we set the value for ivy
const localeId = envInjector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
setLocaleId(localeId || DEFAULT_LOCALE_ID);
if (isApplicationBootstrapConfig(config)) {
const appRef = envInjector.get(ApplicationRef);
if (config.rootComponent !== undefined) {
appRef.bootstrap(config.rootComponent);
}
if (typeof ngDevMode === 'undefined' || ngDevMode) {
const imagePerformanceService = envInjector.get(ImagePerformanceWarning);
imagePerformanceService.start();
}
return appRef;
} else {
moduleDoBootstrap(config.moduleRef, config.allPlatformModules);
return config.moduleRef;
}
});
});
});
}
function moduleDoBootstrap(
moduleRef: InternalNgModuleRef<any>,
allPlatformModules: NgModuleRef<unknown>[],
): void {
const appRef = moduleRef.injector.get(ApplicationRef);
if (moduleRef._bootstrapComponents.length > 0) {
moduleRef._bootstrapComponents.forEach((f) => appRef.bootstrap(f));
} else if (moduleRef.instance.ngDoBootstrap) {
moduleRef.instance.ngDoBootstrap(appRef);
} else {
throw new RuntimeError(
RuntimeErrorCode.BOOTSTRAP_COMPONENTS_NOT_FOUND,
ngDevMode &&
`The module ${stringify(moduleRef.instance.constructor)} was bootstrapped, ` +
`but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. ` +
`Please define one of these.`,
);
}
allPlatformModules.push(moduleRef);
}
+2 -1
View File
@@ -15,7 +15,8 @@ import {InjectionToken, Injector, StaticProvider} from '../di';
import {INJECTOR_SCOPE} from '../di/scope';
import {RuntimeError, RuntimeErrorCode} from '../errors';
import {PLATFORM_DESTROY_LISTENERS, PlatformRef} from './platform_ref';
import {PlatformRef} from './platform_ref';
import {PLATFORM_DESTROY_LISTENERS} from './platform_destroy_listeners';
let _platformInjector: Injector | null = null;
@@ -0,0 +1,19 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {InjectionToken} from '../di';
/**
* Internal token that allows to register extra callbacks that should be invoked during the
* `PlatformRef.destroy` operation. This token is needed to avoid a direct reference to the
* `PlatformRef` class (i.e. register the callback via `PlatformRef.onDestroy`), thus making the
* entire class tree-shakeable.
*/
export const PLATFORM_DESTROY_LISTENERS = new InjectionToken<Set<VoidFunction>>(
ngDevMode ? 'PlatformDestroyListeners' : '',
);
+7 -89
View File
@@ -6,47 +6,27 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ApplicationInitStatus} from '../application/application_init';
import {compileNgModuleFactory} from '../application/application_ngmodule_factory_compiler';
import {
_callAndReportToErrorHandler,
ApplicationRef,
BootstrapOptions,
optionsReducer,
remove,
} from '../application/application_ref';
import {
getNgZoneOptions,
internalProvideZoneChangeDetection,
PROVIDED_NG_ZONE,
} from '../change_detection/scheduling/ng_zone_scheduling';
import {
ChangeDetectionScheduler,
PROVIDED_ZONELESS,
} from '../change_detection/scheduling/zoneless_scheduling';
import {ChangeDetectionScheduler} from '../change_detection/scheduling/zoneless_scheduling';
import {ChangeDetectionSchedulerImpl} from '../change_detection/scheduling/zoneless_scheduling_impl';
import {Injectable, InjectionToken, Injector} from '../di';
import {ErrorHandler} from '../error_handler';
import {Injectable, Injector} from '../di';
import {RuntimeError, RuntimeErrorCode} from '../errors';
import {DEFAULT_LOCALE_ID} from '../i18n/localization';
import {LOCALE_ID} from '../i18n/tokens';
import {Type} from '../interface/type';
import {CompilerOptions} from '../linker';
import {InternalNgModuleRef, NgModuleFactory, NgModuleRef} from '../linker/ng_module_factory';
import {setLocaleId} from '../render3';
import {NgModuleFactory, NgModuleRef} from '../linker/ng_module_factory';
import {createNgModuleRefWithProviders} from '../render3/ng_module_ref';
import {stringify} from '../util/stringify';
import {getNgZone, NgZone, NoopNgZone} from '../zone/ng_zone';
/**
* Internal token that allows to register extra callbacks that should be invoked during the
* `PlatformRef.destroy` operation. This token is needed to avoid a direct reference to the
* `PlatformRef` class (i.e. register the callback via `PlatformRef.onDestroy`), thus making the
* entire class tree-shakeable.
*/
export const PLATFORM_DESTROY_LISTENERS = new InjectionToken<Set<VoidFunction>>(
ngDevMode ? 'PlatformDestroyListeners' : '',
);
import {getNgZone, NgZone} from '../zone/ng_zone';
import {bootstrap} from './bootstrap';
import {PLATFORM_DESTROY_LISTENERS} from './platform_destroy_listeners';
/**
* The Angular platform is the entry point for Angular on a web page.
@@ -97,52 +77,8 @@ export class PlatformRef {
this.injector,
allAppProviders,
);
const envInjector = moduleRef.injector;
const ngZone = envInjector.get(NgZone);
return ngZone.run(() => {
moduleRef.resolveInjectorInitializers();
const exceptionHandler = envInjector.get(ErrorHandler, null);
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (exceptionHandler === null) {
throw new RuntimeError(
RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP,
'No ErrorHandler. Is platform module (BrowserModule) included?',
);
}
if (envInjector.get(PROVIDED_ZONELESS) && envInjector.get(PROVIDED_NG_ZONE)) {
throw new RuntimeError(
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
'Invalid change detection configuration: ' +
'provideZoneChangeDetection and provideExperimentalZonelessChangeDetection cannot be used together.',
);
}
}
ngZone.runOutsideAngular(() => {
const subscription = ngZone.onError.subscribe({
next: (error: any) => {
exceptionHandler!.handleError(error);
},
});
moduleRef.onDestroy(() => {
remove(this._modules, moduleRef);
subscription.unsubscribe();
});
});
return _callAndReportToErrorHandler(exceptionHandler!, ngZone, () => {
const initStatus = envInjector.get(ApplicationInitStatus);
initStatus.runInitializers();
return initStatus.donePromise.then(() => {
// If the `LOCALE_ID` provider is defined at bootstrap then we set the value for ivy
const localeId = envInjector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
setLocaleId(localeId || DEFAULT_LOCALE_ID);
this._moduleDoBootstrap(moduleRef);
return moduleRef;
});
});
});
return bootstrap({moduleRef, allPlatformModules: this._modules});
}
/**
@@ -173,24 +109,6 @@ export class PlatformRef {
);
}
private _moduleDoBootstrap(moduleRef: InternalNgModuleRef<any>): void {
const appRef = moduleRef.injector.get(ApplicationRef);
if (moduleRef._bootstrapComponents.length > 0) {
moduleRef._bootstrapComponents.forEach((f) => appRef.bootstrap(f));
} else if (moduleRef.instance.ngDoBootstrap) {
moduleRef.instance.ngDoBootstrap(appRef);
} else {
throw new RuntimeError(
RuntimeErrorCode.BOOTSTRAP_COMPONENTS_NOT_FOUND,
ngDevMode &&
`The module ${stringify(moduleRef.instance.constructor)} was bootstrapped, ` +
`but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. ` +
`Please define one of these.`,
);
}
this._modules.push(moduleRef);
}
/**
* Registers a listener to be called when the platform is destroyed.
*/