feat(core): introduce createApplication API (#46475)

The `createApplication` function makes it possible to create an
application instance (represented by the `ApplicationRef`)
without bootstrapping any components. It is useful in the
situations where ones wants to decouple and delay components
rendering and / or render multiple root components in one
application. Angular elements can use this API to create
custom element types with an environment linked to a
created application.

PR Close #46475
This commit is contained in:
Pawel Kozlowski
2022-07-06 09:18:29 +02:00
committed by Jessica Janiuk
parent 6fed377140
commit 4b377d3a6d
8 changed files with 76 additions and 38 deletions
+32 -24
View File
@@ -177,7 +177,8 @@ export function runPlatformInitializers(injector: Injector): void {
}
/**
* Internal bootstrap application API that implements the core bootstrap logic.
* Internal create application API that implements the core application creation logic and optional
* bootstrap logic.
*
* Platforms (such as `platform-browser`) may require different set of application and platform
* providers for an application to function correctly. As a result, platforms may use this function
@@ -186,17 +187,20 @@ export function runPlatformInitializers(injector: Injector): void {
*
* @returns A promise that returns an `ApplicationRef` instance once resolved.
*/
export function internalBootstrapApplication(config: {
rootComponent: Type<unknown>,
export function internalCreateApplication(config: {
rootComponent?: Type<unknown>,
appProviders?: Array<Provider|ImportedNgModuleProviders>,
platformProviders?: Provider[],
}): Promise<ApplicationRef> {
const {rootComponent, appProviders, platformProviders} = config;
NG_DEV_MODE && assertStandaloneComponentType(rootComponent);
if (NG_DEV_MODE && rootComponent !== undefined) {
assertStandaloneComponentType(rootComponent);
}
const platformInjector = createOrReusePlatformInjector(platformProviders as StaticProvider[]);
const ngZone = new NgZone(getNgZoneOptions());
const ngZone = getNgZone('zone.js', getNgZoneOptions());
return ngZone.run(() => {
// Create root application injector based on a set of providers configured at the platform
@@ -205,10 +209,11 @@ export function internalBootstrapApplication(config: {
{provide: NgZone, useValue: ngZone}, //
...(appProviders || []), //
];
const appInjector = createEnvironmentInjector(
const envInjector = createEnvironmentInjector(
allAppProviders, platformInjector as EnvironmentInjector, 'Environment Injector');
const exceptionHandler: ErrorHandler|null = appInjector.get(ErrorHandler, null);
const exceptionHandler: ErrorHandler|null = envInjector.get(ErrorHandler, null);
if (NG_DEV_MODE && !exceptionHandler) {
throw new RuntimeError(
RuntimeErrorCode.ERROR_HANDLER_NOT_FOUND,
@@ -223,27 +228,30 @@ export function internalBootstrapApplication(config: {
}
});
});
// 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 = appInjector.get(ApplicationInitStatus);
const initStatus = envInjector.get(ApplicationInitStatus);
initStatus.runInitializers();
return initStatus.donePromise.then(() => {
const localeId = appInjector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
const localeId = envInjector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
setLocaleId(localeId || DEFAULT_LOCALE_ID);
const appRef = appInjector.get(ApplicationRef);
// If the whole platform is destroyed, invoke the `destroy` method
// for all bootstrapped applications as well.
const destroyListener = () => appRef.destroy();
const onPlatformDestroyListeners = platformInjector.get(PLATFORM_DESTROY_LISTENERS, null);
onPlatformDestroyListeners?.add(destroyListener);
appRef.onDestroy(() => {
onPlatformDestroyListeners?.delete(destroyListener);
onErrorSubscription.unsubscribe();
});
appRef.bootstrap(rootComponent);
const appRef = envInjector.get(ApplicationRef);
if (rootComponent !== undefined) {
appRef.bootstrap(rootComponent);
}
return appRef;
});
});
@@ -493,7 +501,7 @@ export class PlatformRef {
}
private _moduleDoBootstrap(moduleRef: InternalNgModuleRef<any>): void {
const appRef = moduleRef.injector.get(ApplicationRef) as ApplicationRef;
const appRef = moduleRef.injector.get(ApplicationRef);
if (moduleRef._bootstrapComponents.length > 0) {
moduleRef._bootstrapComponents.forEach(f => appRef.bootstrap(f));
} else if (moduleRef.instance.ngDoBootstrap) {
+1 -1
View File
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
export {ALLOW_MULTIPLE_PLATFORMS as ɵALLOW_MULTIPLE_PLATFORMS, internalBootstrapApplication as ɵinternalBootstrapApplication} from './application_ref';
export {ALLOW_MULTIPLE_PLATFORMS as ɵALLOW_MULTIPLE_PLATFORMS, internalCreateApplication as ɵinternalCreateApplication} from './application_ref';
export {APP_ID_RANDOM_PROVIDER as ɵAPP_ID_RANDOM_PROVIDER} from './application_tokens';
export {defaultIterableDiffers as ɵdefaultIterableDiffers, defaultKeyValueDiffers as ɵdefaultKeyValueDiffers} from './change_detection/change_detection';
export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetectionStrategy as ɵisDefaultChangeDetectionStrategy} from './change_detection/constants';
+9 -3
View File
@@ -333,20 +333,26 @@ class SomeComponent {
withModule(
{providers},
waitForAsync(inject([EnvironmentInjector], (parentInjector: EnvironmentInjector) => {
// This is a temporary type to represent an instance of an R3Injector, which
// can be destroyed.
// The type will be replaced with a different one once destroyable injector
// type is available.
type DestroyableInjector = EnvironmentInjector&{destroyed?: boolean};
createRootEl();
const injector = createApplicationRefInjector(parentInjector);
const injector = createApplicationRefInjector(parentInjector) as DestroyableInjector;
const appRef = injector.get(ApplicationRef);
appRef.bootstrap(SomeComponent);
expect(appRef.destroyed).toBeFalse();
expect((injector as any).destroyed).toBeFalse();
expect(injector.destroyed).toBeFalse();
appRef.destroy();
expect(appRef.destroyed).toBeTrue();
expect((injector as any).destroyed).toBeTrue();
expect(injector.destroyed).toBeTrue();
}))));
});