diff --git a/packages/core/src/application_ref.ts b/packages/core/src/application_ref.ts index 4b9365c17e74..7c916deca9de 100644 --- a/packages/core/src/application_ref.ts +++ b/packages/core/src/application_ref.ts @@ -216,76 +216,80 @@ export function internalCreateApplication(config: { appProviders?: Array, platformProviders?: Provider[], }): Promise { - const {rootComponent, appProviders, platformProviders} = config; - - if ((typeof ngDevMode === 'undefined' || ngDevMode) && rootComponent !== undefined) { - assertStandaloneComponentType(rootComponent); - } + try { + const {rootComponent, appProviders, platformProviders} = config; - const platformInjector = createOrReusePlatformInjector(platformProviders as StaticProvider[]); + if ((typeof ngDevMode === 'undefined' || ngDevMode) && rootComponent !== undefined) { + assertStandaloneComponentType(rootComponent); + } - // Create root application injector based on a set of providers configured at the platform - // bootstrap level as well as providers passed to the bootstrap call by a user. - const allAppProviders = [ - provideZoneChangeDetection(), - ...(appProviders || []), - ]; - const adapter = new EnvironmentNgModuleRefAdapter({ - providers: allAppProviders, - parent: platformInjector as EnvironmentInjector, - debugName: (typeof ngDevMode === 'undefined' || ngDevMode) ? 'Environment Injector' : '', - // We skip environment initializers because we need to run them inside the NgZone, which happens - // after we get the NgZone instance from the Injector. - runEnvironmentInitializers: false, - }); - const envInjector = adapter.injector; - const ngZone = envInjector.get(NgZone); + const platformInjector = createOrReusePlatformInjector(platformProviders as StaticProvider[]); + + // Create root application injector based on a set of providers configured at the platform + // bootstrap level as well as providers passed to the bootstrap call by a user. + const allAppProviders = [ + provideZoneChangeDetection(), + ...(appProviders || []), + ]; + const adapter = new EnvironmentNgModuleRefAdapter({ + providers: allAppProviders, + parent: platformInjector as EnvironmentInjector, + debugName: (typeof ngDevMode === 'undefined' || ngDevMode) ? 'Environment Injector' : '', + // We skip environment initializers because we need to run them inside the NgZone, which + // 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) && !exceptionHandler) { - throw new RuntimeError( - RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP, - 'No `ErrorHandler` found in the Dependency Injection tree.'); - } + return ngZone.run(() => { + envInjector.resolveInjectorInitializers(); + const exceptionHandler: ErrorHandler|null = envInjector.get(ErrorHandler, null); + if ((typeof ngDevMode === 'undefined' || ngDevMode) && !exceptionHandler) { + throw new RuntimeError( + RuntimeErrorCode.MISSING_REQUIRED_INJECTABLE_IN_BOOTSTRAP, + 'No `ErrorHandler` found in the Dependency Injection tree.'); + } - let onErrorSubscription: Subscription; - ngZone.runOutsideAngular(() => { - onErrorSubscription = ngZone.onError.subscribe({ - next: (error: any) => { - exceptionHandler!.handleError(error); - } + 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); + // 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); - }); + envInjector.onDestroy(() => { + onErrorSubscription.unsubscribe(); + onPlatformDestroyListeners.delete(destroyListener); + }); - return _callAndReportToErrorHandler(exceptionHandler!, ngZone, () => { - const initStatus = envInjector.get(ApplicationInitStatus); - initStatus.runInitializers(); + 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); + 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); - } - return appRef; + const appRef = envInjector.get(ApplicationRef); + if (rootComponent !== undefined) { + appRef.bootstrap(rootComponent); + } + return appRef; + }); }); }); - }); + } catch (e) { + return Promise.reject(e); + } } /** diff --git a/packages/platform-browser/test/browser/bootstrap_spec.ts b/packages/platform-browser/test/browser/bootstrap_spec.ts index 901e02f15cf9..0ca4d21023b5 100644 --- a/packages/platform-browser/test/browser/bootstrap_spec.ts +++ b/packages/platform-browser/test/browser/bootstrap_spec.ts @@ -8,7 +8,7 @@ import {animate, style, transition, trigger} from '@angular/animations'; import {DOCUMENT, isPlatformBrowser, ɵgetDOM as getDOM} from '@angular/common'; -import {ANIMATION_MODULE_TYPE, APP_INITIALIZER, Compiler, Component, createPlatformFactory, CUSTOM_ELEMENTS_SCHEMA, Directive, ErrorHandler, Inject, inject as _inject, InjectionToken, Injector, LOCALE_ID, NgModule, NgModuleRef, NgZone, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Provider, Sanitizer, StaticProvider, Testability, TestabilityRegistry, TransferState, Type, VERSION} from '@angular/core'; +import {ANIMATION_MODULE_TYPE, APP_INITIALIZER, Compiler, Component, createPlatformFactory, CUSTOM_ELEMENTS_SCHEMA, Directive, ErrorHandler, importProvidersFrom, Inject, inject as _inject, InjectionToken, Injector, LOCALE_ID, NgModule, NgModuleRef, NgZone, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Provider, Sanitizer, StaticProvider, Testability, TestabilityRegistry, TransferState, Type, VERSION} from '@angular/core'; import {ApplicationRef, destroyPlatform, provideZoneChangeDetection} from '@angular/core/src/application_ref'; import {Console} from '@angular/core/src/console'; import {ComponentRef} from '@angular/core/src/linker/component_factory'; @@ -288,14 +288,22 @@ function bootstrap( expect(el2.innerText).toBe('Hello from Updated NonStandaloneComp!'); }); - it('should throw when trying to bootstrap a non-standalone component', () => { + it('should throw when trying to bootstrap a non-standalone component', async () => { const msg = 'NG0907: The NonStandaloneComp component is not marked as standalone, ' + 'but Angular expects to have a standalone component here. Please make sure the ' + 'NonStandaloneComp component has the `standalone: true` flag in the decorator.'; - expect(() => bootstrapApplication(NonStandaloneComp)).toThrowError(msg); + let bootstrapError: string|null = null; + + try { + await bootstrapApplication(NonStandaloneComp); + } catch (e) { + bootstrapError = (e as Error).message; + } + + expect(bootstrapError).toBe(msg); }); - it('should throw when trying to bootstrap a standalone directive', () => { + it('should throw when trying to bootstrap a standalone directive', async () => { @Directive({ standalone: true, selector: '[dir]', @@ -306,15 +314,31 @@ function bootstrap( const msg = // 'NG0906: The StandaloneDirective is not an Angular component, ' + 'make sure it has the `@Component` decorator.'; - expect(() => bootstrapApplication(StandaloneDirective)).toThrowError(msg); + let bootstrapError: string|null = null; + + try { + await bootstrapApplication(StandaloneDirective); + } catch (e) { + bootstrapError = (e as Error).message; + } + + expect(bootstrapError).toBe(msg); }); - it('should throw when trying to bootstrap a non-annotated class', () => { + it('should throw when trying to bootstrap a non-annotated class', async () => { class NonAnnotatedClass {} const msg = // 'NG0906: The NonAnnotatedClass is not an Angular component, ' + 'make sure it has the `@Component` decorator.'; - expect(() => bootstrapApplication(NonAnnotatedClass)).toThrowError(msg); + let bootstrapError: string|null = null; + + try { + await bootstrapApplication(NonAnnotatedClass); + } catch (e) { + bootstrapError = (e as Error).message; + } + + expect(bootstrapError).toBe(msg); }); it('should have the TransferState token available', async () => { @@ -334,14 +358,25 @@ function bootstrap( expect(state).toBeInstanceOf(TransferState); }); + it('should reject the bootstrapApplication promise if an imported module throws', (done) => { + @NgModule() + class ErrorModule { + constructor() { + throw new Error('This error should be in the promise rejection'); + } + } + + bootstrapApplication(SimpleComp, { + providers: [importProvidersFrom(ErrorModule)] + }).then(() => done.fail('Expected bootstrap promised to be rejected'), () => done()); + }); + describe('with animations', () => { @Component({ standalone: true, selector: 'hello-app', - template: ` -
Hello from AnimationCmp!
`, + template: + '
Hello from AnimationCmp!
', animations: [trigger( 'myAnimation', [transition('void => *', [style({opacity: 1}), animate(5)])])], })