mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
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:
parent
6fed377140
commit
4b377d3a6d
8 changed files with 76 additions and 38 deletions
|
|
@ -62,6 +62,9 @@ export class By {
|
|||
static directive(type: Type<any>): Predicate<DebugNode>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function createApplication(options?: ApplicationConfig): Promise<ApplicationRef>;
|
||||
|
||||
// @public
|
||||
export function disableDebugTools(): void;
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}))));
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {CommonModule, DOCUMENT, XhrFactory, ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common';
|
||||
import {APP_ID, ApplicationModule, ApplicationRef, createPlatformFactory, ErrorHandler, ImportedNgModuleProviders, Inject, InjectionToken, ModuleWithProviders, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, platformCore, PlatformRef, Provider, RendererFactory2, SkipSelf, StaticProvider, Testability, TestabilityRegistry, Type, ɵINJECTOR_SCOPE as INJECTOR_SCOPE, ɵinternalBootstrapApplication as internalBootstrapApplication, ɵsetDocument, ɵTESTABILITY as TESTABILITY, ɵTESTABILITY_GETTER as TESTABILITY_GETTER} from '@angular/core';
|
||||
import {APP_ID, ApplicationModule, ApplicationRef, createPlatformFactory, ErrorHandler, ImportedNgModuleProviders, Inject, InjectionToken, ModuleWithProviders, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, platformCore, PlatformRef, Provider, RendererFactory2, SkipSelf, StaticProvider, Testability, TestabilityRegistry, Type, ɵINJECTOR_SCOPE as INJECTOR_SCOPE, ɵinternalCreateApplication as internalCreateApplication, ɵsetDocument, ɵTESTABILITY as TESTABILITY, ɵTESTABILITY_GETTER as TESTABILITY_GETTER} from '@angular/core';
|
||||
|
||||
import {BrowserDomAdapter} from './browser/browser_adapter';
|
||||
import {SERVER_TRANSITION_PROVIDERS, TRANSITION_ID} from './browser/server-transition';
|
||||
|
|
@ -22,7 +22,7 @@ import {DomSharedStylesHost, SharedStylesHost} from './dom/shared_styles_host';
|
|||
const NG_DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;
|
||||
|
||||
/**
|
||||
* Set of config options available during the bootstrap operation via `bootstrapApplication` call.
|
||||
* Set of config options available during the application bootstrap operation.
|
||||
*
|
||||
* @developerPreview
|
||||
* @publicApi
|
||||
|
|
@ -96,14 +96,34 @@ export interface ApplicationConfig {
|
|||
*/
|
||||
export function bootstrapApplication(
|
||||
rootComponent: Type<unknown>, options?: ApplicationConfig): Promise<ApplicationRef> {
|
||||
return internalBootstrapApplication({
|
||||
rootComponent,
|
||||
return internalCreateApplication({rootComponent, ...createProvidersConfig(options)});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of an Angular application without bootstrapping any components. This is useful
|
||||
* for the situation where one wants to decouple application environment creation (a platform and
|
||||
* associated injectors) from rendering components on a screen. Components can be subsequently
|
||||
* bootstrapped on the returned `ApplicationRef`.
|
||||
*
|
||||
* @param options Extra configuration for the application environment, see `ApplicationConfig` for
|
||||
* additional info.
|
||||
* @returns A promise that returns an `ApplicationRef` instance once resolved.
|
||||
*
|
||||
* @publicApi
|
||||
* @developerPreview
|
||||
*/
|
||||
export function createApplication(options?: ApplicationConfig) {
|
||||
return internalCreateApplication(createProvidersConfig(options));
|
||||
}
|
||||
|
||||
function createProvidersConfig(options?: ApplicationConfig) {
|
||||
return {
|
||||
appProviders: [
|
||||
...BROWSER_MODULE_PROVIDERS,
|
||||
...(options?.providers ?? []),
|
||||
],
|
||||
platformProviders: INTERNAL_BROWSER_PLATFORM_PROVIDERS,
|
||||
});
|
||||
platformProviders: INTERNAL_BROWSER_PLATFORM_PROVIDERS
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {ApplicationConfig, bootstrapApplication, BrowserModule, platformBrowser, provideProtractorTestingSupport} from './browser';
|
||||
export {ApplicationConfig, bootstrapApplication, BrowserModule, createApplication, platformBrowser, provideProtractorTestingSupport} from './browser';
|
||||
export {Meta, MetaDefinition} from './browser/meta';
|
||||
export {Title} from './browser/title';
|
||||
export {disableDebugTools, enableDebugTools} from './browser/tools/tools';
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ApplicationRef, ImportedNgModuleProviders, importProvidersFrom, NgModuleFactory, NgModuleRef, PlatformRef, Provider, StaticProvider, Type, ɵinternalBootstrapApplication as internalBootstrapApplication, ɵisPromise} from '@angular/core';
|
||||
import {ApplicationRef, ImportedNgModuleProviders, importProvidersFrom, NgModuleFactory, NgModuleRef, PlatformRef, Provider, StaticProvider, Type, ɵinternalCreateApplication as internalCreateApplication, ɵisPromise} from '@angular/core';
|
||||
import {BrowserModule, ɵTRANSITION_ID} from '@angular/platform-browser';
|
||||
import {first} from 'rxjs/operators';
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ export function renderApplication<T>(rootComponent: Type<T>, options: {
|
|||
importProvidersFrom(ServerModule),
|
||||
...(options.providers ?? []),
|
||||
];
|
||||
return _render(platform, internalBootstrapApplication({rootComponent, appProviders}));
|
||||
return _render(platform, internalCreateApplication({rootComponent, appProviders}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -740,7 +740,8 @@ describe('platform-server integration', () => {
|
|||
|
||||
// Run the set of tests with regular and standalone components.
|
||||
[true, false].forEach((isStandalone: boolean) => {
|
||||
it('using renderModule should work', waitForAsync(() => {
|
||||
it(`using ${isStandalone ? 'renderApplication' : 'renderModule'} should work`,
|
||||
waitForAsync(() => {
|
||||
const options = {document: doc};
|
||||
const bootstrap = isStandalone ?
|
||||
renderApplication(MyAsyncServerAppStandalone, {...options, appId: 'simple-cmp'}) :
|
||||
|
|
|
|||
Loading…
Reference in a new issue