Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 62 additions & 58 deletions packages/core/src/application_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,76 +216,80 @@ export function internalCreateApplication(config: {
appProviders?: Array<Provider|EnvironmentProviders>,
platformProviders?: Provider[],
}): Promise<ApplicationRef> {
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);
}
}

/**
Expand Down
57 changes: 46 additions & 11 deletions packages/platform-browser/test/browser/bootstrap_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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]',
Expand All @@ -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 () => {
Expand All @@ -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: `
<div
@myAnimation
(@myAnimation.start)="onStart($event)">Hello from AnimationCmp!</div>`,
template:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated change, but the backticks were messing with the syntax highlighting in vscode.

'<div @myAnimation (@myAnimation.start)="onStart($event)">Hello from AnimationCmp!</div>',
animations: [trigger(
'myAnimation', [transition('void => *', [style({opacity: 1}), animate(5)])])],
})
Expand Down