Skip to content

Commit 3da0254

Browse files
atscottthePunderWoman
authored andcommitted
refactor(core): de-duplicate bootstrap code between bootstrapApplication and bootstrapModule (angular#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 angular#57060
1 parent 3459289 commit 3da0254

File tree

5 files changed

+194
-168
lines changed

5 files changed

+194
-168
lines changed

packages/core/src/application/create_application.ts

Lines changed: 7 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,19 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Subscription} from 'rxjs';
10-
11-
import {
12-
internalProvideZoneChangeDetection,
13-
PROVIDED_NG_ZONE,
14-
} from '../change_detection/scheduling/ng_zone_scheduling';
9+
import {internalProvideZoneChangeDetection} from '../change_detection/scheduling/ng_zone_scheduling';
1510
import {EnvironmentProviders, Provider, StaticProvider} from '../di/interface/provider';
1611
import {EnvironmentInjector} from '../di/r3_injector';
17-
import {ErrorHandler} from '../error_handler';
18-
import {RuntimeError, RuntimeErrorCode} from '../errors';
19-
import {DEFAULT_LOCALE_ID} from '../i18n/localization';
20-
import {LOCALE_ID} from '../i18n/tokens';
21-
import {ImagePerformanceWarning} from '../image_performance_warning';
2212
import {Type} from '../interface/type';
2313
import {createOrReusePlatformInjector} from '../platform/platform';
24-
import {PLATFORM_DESTROY_LISTENERS} from '../platform/platform_ref';
2514
import {assertStandaloneComponentType} from '../render3/errors';
26-
import {setLocaleId} from '../render3/i18n/i18n_locale_id';
2715
import {EnvironmentNgModuleRefAdapter} from '../render3/ng_module_ref';
2816
import {NgZone} from '../zone/ng_zone';
2917

30-
import {ApplicationInitStatus} from './application_init';
3118
import {_callAndReportToErrorHandler, ApplicationRef} from './application_ref';
32-
import {
33-
PROVIDED_ZONELESS,
34-
ChangeDetectionScheduler,
35-
} from '../change_detection/scheduling/zoneless_scheduling';
19+
import {ChangeDetectionScheduler} from '../change_detection/scheduling/zoneless_scheduling';
3620
import {ChangeDetectionSchedulerImpl} from '../change_detection/scheduling/zoneless_scheduling_impl';
21+
import {bootstrap} from '../platform/bootstrap';
3722

3823
/**
3924
* Internal create application API that implements the core application creation logic and optional
@@ -76,67 +61,11 @@ export function internalCreateApplication(config: {
7661
// happens after we get the NgZone instance from the Injector.
7762
runEnvironmentInitializers: false,
7863
});
79-
const envInjector = adapter.injector;
80-
const ngZone = envInjector.get(NgZone);
81-
82-
return ngZone.run(() => {
83-
envInjector.resolveInjectorInitializers();
84-
const exceptionHandler: ErrorHandler | null = envInjector.get(ErrorHandler, null);
85-
if (typeof ngDevMode === 'undefined' || ngDevMode) {
86-
if (!exceptionHandler) {
87-
throw new RuntimeError(
88-
RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP,
89-
'No `ErrorHandler` found in the Dependency Injection tree.',
90-
);
91-
}
92-
if (envInjector.get(PROVIDED_ZONELESS) && envInjector.get(PROVIDED_NG_ZONE)) {
93-
throw new RuntimeError(
94-
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
95-
'Invalid change detection configuration: ' +
96-
'provideZoneChangeDetection and provideExperimentalZonelessChangeDetection cannot be used together.',
97-
);
98-
}
99-
}
100-
101-
let onErrorSubscription: Subscription;
102-
ngZone.runOutsideAngular(() => {
103-
onErrorSubscription = ngZone.onError.subscribe({
104-
next: (error: any) => {
105-
exceptionHandler!.handleError(error);
106-
},
107-
});
108-
});
109-
110-
// If the whole platform is destroyed, invoke the `destroy` method
111-
// for all bootstrapped applications as well.
112-
const destroyListener = () => envInjector.destroy();
113-
const onPlatformDestroyListeners = platformInjector.get(PLATFORM_DESTROY_LISTENERS);
114-
onPlatformDestroyListeners.add(destroyListener);
115-
116-
envInjector.onDestroy(() => {
117-
onErrorSubscription.unsubscribe();
118-
onPlatformDestroyListeners.delete(destroyListener);
119-
});
120-
121-
return _callAndReportToErrorHandler(exceptionHandler!, ngZone, () => {
122-
const initStatus = envInjector.get(ApplicationInitStatus);
123-
initStatus.runInitializers();
124-
125-
return initStatus.donePromise.then(() => {
126-
const localeId = envInjector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
127-
setLocaleId(localeId || DEFAULT_LOCALE_ID);
12864

129-
const appRef = envInjector.get(ApplicationRef);
130-
if (rootComponent !== undefined) {
131-
appRef.bootstrap(rootComponent);
132-
}
133-
if (typeof ngDevMode === 'undefined' || ngDevMode) {
134-
const imagePerformanceService = envInjector.get(ImagePerformanceWarning);
135-
imagePerformanceService.start();
136-
}
137-
return appRef;
138-
});
139-
});
65+
return bootstrap({
66+
r3Injector: adapter.injector,
67+
platformInjector,
68+
rootComponent,
14069
});
14170
} catch (e) {
14271
return Promise.reject(e);
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import {Subscription} from 'rxjs';
9+
10+
import {PROVIDED_NG_ZONE} from '../change_detection/scheduling/ng_zone_scheduling';
11+
import {EnvironmentInjector, R3Injector} from '../di/r3_injector';
12+
import {ErrorHandler} from '../error_handler';
13+
import {RuntimeError, RuntimeErrorCode} from '../errors';
14+
import {DEFAULT_LOCALE_ID} from '../i18n/localization';
15+
import {LOCALE_ID} from '../i18n/tokens';
16+
import {ImagePerformanceWarning} from '../image_performance_warning';
17+
import {Type} from '../interface/type';
18+
import {PLATFORM_DESTROY_LISTENERS} from './platform_destroy_listeners';
19+
import {setLocaleId} from '../render3/i18n/i18n_locale_id';
20+
import {NgZone} from '../zone/ng_zone';
21+
22+
import {ApplicationInitStatus} from '../application/application_init';
23+
import {_callAndReportToErrorHandler, ApplicationRef, remove} from '../application/application_ref';
24+
import {PROVIDED_ZONELESS} from '../change_detection/scheduling/zoneless_scheduling';
25+
import {Injector} from '../di';
26+
import {InternalNgModuleRef, NgModuleRef} from '../linker/ng_module_factory';
27+
import {stringify} from '../util/stringify';
28+
29+
export interface ModuleBootstrapConfig<M> {
30+
moduleRef: InternalNgModuleRef<M>;
31+
allPlatformModules: NgModuleRef<unknown>[];
32+
}
33+
34+
export interface ApplicationBootstrapConfig {
35+
r3Injector: R3Injector;
36+
platformInjector: Injector;
37+
rootComponent: Type<unknown> | undefined;
38+
}
39+
40+
function isApplicationBootstrapConfig(
41+
config: ApplicationBootstrapConfig | ModuleBootstrapConfig<unknown>,
42+
): config is ApplicationBootstrapConfig {
43+
return !!(config as ApplicationBootstrapConfig).platformInjector;
44+
}
45+
46+
export function bootstrap<M>(
47+
moduleBootstrapConfig: ModuleBootstrapConfig<M>,
48+
): Promise<NgModuleRef<M>>;
49+
export function bootstrap(
50+
applicationBootstrapConfig: ApplicationBootstrapConfig,
51+
): Promise<ApplicationRef>;
52+
export function bootstrap<M>(
53+
config: ModuleBootstrapConfig<M> | ApplicationBootstrapConfig,
54+
): Promise<ApplicationRef> | Promise<NgModuleRef<M>> {
55+
const envInjector = isApplicationBootstrapConfig(config)
56+
? config.r3Injector
57+
: config.moduleRef.injector;
58+
const ngZone = envInjector.get(NgZone);
59+
return ngZone.run(() => {
60+
if (isApplicationBootstrapConfig(config)) {
61+
config.r3Injector.resolveInjectorInitializers();
62+
} else {
63+
config.moduleRef.resolveInjectorInitializers();
64+
}
65+
const exceptionHandler = envInjector.get(ErrorHandler, null);
66+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
67+
if (exceptionHandler === null) {
68+
const errorMessage = isApplicationBootstrapConfig(config)
69+
? 'No `ErrorHandler` found in the Dependency Injection tree.'
70+
: 'No ErrorHandler. Is platform module (BrowserModule) included';
71+
throw new RuntimeError(
72+
RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP,
73+
errorMessage,
74+
);
75+
}
76+
if (envInjector.get(PROVIDED_ZONELESS) && envInjector.get(PROVIDED_NG_ZONE)) {
77+
throw new RuntimeError(
78+
RuntimeErrorCode.PROVIDED_BOTH_ZONE_AND_ZONELESS,
79+
'Invalid change detection configuration: ' +
80+
'provideZoneChangeDetection and provideExperimentalZonelessChangeDetection cannot be used together.',
81+
);
82+
}
83+
}
84+
85+
let onErrorSubscription: Subscription;
86+
ngZone.runOutsideAngular(() => {
87+
onErrorSubscription = ngZone.onError.subscribe({
88+
next: (error: any) => {
89+
exceptionHandler!.handleError(error);
90+
},
91+
});
92+
});
93+
94+
if (isApplicationBootstrapConfig(config)) {
95+
// If the whole platform is destroyed, invoke the `destroy` method
96+
// for all bootstrapped applications as well.
97+
const destroyListener = () => envInjector.destroy();
98+
const onPlatformDestroyListeners = config.platformInjector.get(PLATFORM_DESTROY_LISTENERS);
99+
onPlatformDestroyListeners.add(destroyListener);
100+
101+
envInjector.onDestroy(() => {
102+
onErrorSubscription.unsubscribe();
103+
onPlatformDestroyListeners.delete(destroyListener);
104+
});
105+
} else {
106+
config.moduleRef.onDestroy(() => {
107+
remove(config.allPlatformModules, config.moduleRef);
108+
onErrorSubscription.unsubscribe();
109+
});
110+
}
111+
112+
return _callAndReportToErrorHandler(exceptionHandler!, ngZone, () => {
113+
const initStatus = envInjector.get(ApplicationInitStatus);
114+
initStatus.runInitializers();
115+
116+
return initStatus.donePromise.then(() => {
117+
// If the `LOCALE_ID` provider is defined at bootstrap then we set the value for ivy
118+
const localeId = envInjector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
119+
setLocaleId(localeId || DEFAULT_LOCALE_ID);
120+
121+
if (isApplicationBootstrapConfig(config)) {
122+
const appRef = envInjector.get(ApplicationRef);
123+
if (config.rootComponent !== undefined) {
124+
appRef.bootstrap(config.rootComponent);
125+
}
126+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
127+
const imagePerformanceService = envInjector.get(ImagePerformanceWarning);
128+
imagePerformanceService.start();
129+
}
130+
return appRef;
131+
} else {
132+
moduleDoBootstrap(config.moduleRef, config.allPlatformModules);
133+
return config.moduleRef;
134+
}
135+
});
136+
});
137+
});
138+
}
139+
140+
function moduleDoBootstrap(
141+
moduleRef: InternalNgModuleRef<any>,
142+
allPlatformModules: NgModuleRef<unknown>[],
143+
): void {
144+
const appRef = moduleRef.injector.get(ApplicationRef);
145+
if (moduleRef._bootstrapComponents.length > 0) {
146+
moduleRef._bootstrapComponents.forEach((f) => appRef.bootstrap(f));
147+
} else if (moduleRef.instance.ngDoBootstrap) {
148+
moduleRef.instance.ngDoBootstrap(appRef);
149+
} else {
150+
throw new RuntimeError(
151+
RuntimeErrorCode.BOOTSTRAP_COMPONENTS_NOT_FOUND,
152+
ngDevMode &&
153+
`The module ${stringify(moduleRef.instance.constructor)} was bootstrapped, ` +
154+
`but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. ` +
155+
`Please define one of these.`,
156+
);
157+
}
158+
allPlatformModules.push(moduleRef);
159+
}

packages/core/src/platform/platform.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {InjectionToken, Injector, StaticProvider} from '../di';
1515
import {INJECTOR_SCOPE} from '../di/scope';
1616
import {RuntimeError, RuntimeErrorCode} from '../errors';
1717

18-
import {PLATFORM_DESTROY_LISTENERS, PlatformRef} from './platform_ref';
18+
import {PlatformRef} from './platform_ref';
19+
import {PLATFORM_DESTROY_LISTENERS} from './platform_destroy_listeners';
1920

2021
let _platformInjector: Injector | null = null;
2122

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {InjectionToken} from '../di';
10+
11+
/**
12+
* Internal token that allows to register extra callbacks that should be invoked during the
13+
* `PlatformRef.destroy` operation. This token is needed to avoid a direct reference to the
14+
* `PlatformRef` class (i.e. register the callback via `PlatformRef.onDestroy`), thus making the
15+
* entire class tree-shakeable.
16+
*/
17+
export const PLATFORM_DESTROY_LISTENERS = new InjectionToken<Set<VoidFunction>>(
18+
ngDevMode ? 'PlatformDestroyListeners' : '',
19+
);

0 commit comments

Comments
 (0)