feat(bootstrap): add platform and app initializers

Often some init logic needs to run when a platform or an application is boostrapped.
For example, boostraping a platform requires initializing the dom adapter.
Now, it can be done as follows:

new Provider(PLATFORM_INITIALIZER, {useValue: initDomAdapter, multi: true}),

All platform initializers will be run after the platform injector has been created.

Similarly, all application initializers will be run after the app injector has been
created.

Closes #5355
This commit is contained in:
vsavkin 2015-11-18 09:18:37 -08:00 committed by Victor Savkin
parent 3fa287aae2
commit 3c43a8c549
9 changed files with 80 additions and 19 deletions

View File

@ -9,6 +9,7 @@ export 'package:angular2/src/common/pipes.dart';
export 'package:angular2/src/facade/facade.dart'; export 'package:angular2/src/facade/facade.dart';
export 'package:angular2/src/core/application_ref.dart' export 'package:angular2/src/core/application_ref.dart'
hide ApplicationRef_, PlatformRef_; hide ApplicationRef_, PlatformRef_;
export 'package:angular2/src/core/application_tokens.dart' show APP_ID, APP_COMPONENT, APP_INITIALIZER, PLATFORM_INITIALIZER;
export 'package:angular2/src/core/linker.dart'; export 'package:angular2/src/core/linker.dart';
export 'package:angular2/src/core/zone.dart'; export 'package:angular2/src/core/zone.dart';
export 'package:angular2/src/core/render.dart'; export 'package:angular2/src/core/render.dart';

View File

@ -10,7 +10,12 @@ export * from './src/common/pipes';
export * from './src/facade/facade'; export * from './src/facade/facade';
export * from './src/core/linker'; export * from './src/core/linker';
export {platform, createNgZone, PlatformRef, ApplicationRef} from './src/core/application_ref'; export {platform, createNgZone, PlatformRef, ApplicationRef} from './src/core/application_ref';
export {APP_ID, APP_COMPONENT} from './src/core/application_tokens'; export {
APP_ID,
APP_COMPONENT,
APP_INITIALIZER,
PLATFORM_INITIALIZER
} from './src/core/application_tokens';
export * from './src/core/zone'; export * from './src/core/zone';
export * from './src/core/render'; export * from './src/core/render';
export * from './src/common/directives'; export * from './src/common/directives';

View File

@ -13,8 +13,7 @@ import {Type, isPresent, CONST_EXPR} from 'angular2/src/facade/lang';
import {Promise} from 'angular2/src/facade/promise'; import {Promise} from 'angular2/src/facade/promise';
import { import {
BROWSER_PROVIDERS, BROWSER_PROVIDERS,
BROWSER_APP_COMMON_PROVIDERS, BROWSER_APP_COMMON_PROVIDERS
initDomAdapter
} from 'angular2/src/platform/browser_common'; } from 'angular2/src/platform/browser_common';
import {COMPILER_PROVIDERS} from 'angular2/compiler'; import {COMPILER_PROVIDERS} from 'angular2/compiler';
import {ComponentRef, platform, reflector} from 'angular2/core'; import {ComponentRef, platform, reflector} from 'angular2/core';
@ -120,8 +119,6 @@ export function bootstrap(
appComponentType: Type, appComponentType: Type,
customProviders?: Array<any /*Type | Provider | any[]*/>): Promise<ComponentRef> { customProviders?: Array<any /*Type | Provider | any[]*/>): Promise<ComponentRef> {
reflector.reflectionCapabilities = new ReflectionCapabilities(); reflector.reflectionCapabilities = new ReflectionCapabilities();
initDomAdapter();
let appProviders = let appProviders =
isPresent(customProviders) ? [BROWSER_APP_PROVIDERS, customProviders] : BROWSER_APP_PROVIDERS; isPresent(customProviders) ? [BROWSER_APP_PROVIDERS, customProviders] : BROWSER_APP_PROVIDERS;
return platform(BROWSER_PROVIDERS).application(appProviders).bootstrap(appComponentType); return platform(BROWSER_PROVIDERS).application(appProviders).bootstrap(appComponentType);

View File

@ -12,8 +12,7 @@ import {Type, isPresent, CONST_EXPR} from 'angular2/src/facade/lang';
import {Promise} from 'angular2/src/facade/promise'; import {Promise} from 'angular2/src/facade/promise';
import { import {
BROWSER_PROVIDERS, BROWSER_PROVIDERS,
BROWSER_APP_COMMON_PROVIDERS, BROWSER_APP_COMMON_PROVIDERS
initDomAdapter
} from 'angular2/src/platform/browser_common'; } from 'angular2/src/platform/browser_common';
import {ComponentRef, platform, reflector} from 'angular2/core'; import {ComponentRef, platform, reflector} from 'angular2/core';
@ -31,7 +30,6 @@ export const BROWSER_APP_PROVIDERS: Array<any /*Type | Provider | any[]*/> =
export function bootstrapStatic(appComponentType: Type, export function bootstrapStatic(appComponentType: Type,
customProviders?: Array<any /*Type | Provider | any[]*/>, customProviders?: Array<any /*Type | Provider | any[]*/>,
initReflector?: Function): Promise<ComponentRef> { initReflector?: Function): Promise<ComponentRef> {
initDomAdapter();
if (isPresent(initReflector)) { if (isPresent(initReflector)) {
initReflector(); initReflector();
} }

View File

@ -4,7 +4,9 @@ import {provide, Provider, Injector, OpaqueToken} from 'angular2/src/core/di';
import { import {
APP_COMPONENT_REF_PROMISE, APP_COMPONENT_REF_PROMISE,
APP_COMPONENT, APP_COMPONENT,
APP_ID_RANDOM_PROVIDER APP_ID_RANDOM_PROVIDER,
PLATFORM_INITIALIZER,
APP_INITIALIZER
} from './application_tokens'; } from './application_tokens';
import { import {
Promise, Promise,
@ -30,7 +32,6 @@ import {wtfLeave, wtfCreateScope, WtfScopeFn} from './profile/profile';
import {ChangeDetectorRef} from 'angular2/src/core/change_detection/change_detector_ref'; import {ChangeDetectorRef} from 'angular2/src/core/change_detection/change_detector_ref';
import {lockDevMode} from 'angular2/src/facade/lang'; import {lockDevMode} from 'angular2/src/facade/lang';
/** /**
* Construct providers specific to an individual root component. * Construct providers specific to an individual root component.
*/ */
@ -103,15 +104,32 @@ export function platform(providers?: Array<Type | Provider | any[]>): PlatformRe
} }
} }
/**
* Dispose the existing platform.
*/
export function disposePlatform(): void {
if (isPresent(_platform)) {
_platform.dispose();
_platform = null;
}
}
function _createPlatform(providers?: Array<Type | Provider | any[]>): PlatformRef { function _createPlatform(providers?: Array<Type | Provider | any[]>): PlatformRef {
_platformProviders = providers; _platformProviders = providers;
_platform = new PlatformRef_(Injector.resolveAndCreate(providers), () => { let injector = Injector.resolveAndCreate(providers);
_platform = new PlatformRef_(injector, () => {
_platform = null; _platform = null;
_platformProviders = null; _platformProviders = null;
}); });
_runPlatformInitializers(injector);
return _platform; return _platform;
} }
function _runPlatformInitializers(injector: Injector): void {
let inits: Function[] = injector.getOptional(PLATFORM_INITIALIZER);
if (isPresent(inits)) inits.forEach(init => init());
}
/** /**
* The Angular platform is the entry point for Angular on a web page. Each page * The Angular platform is the entry point for Angular on a web page. Each page
* has exactly one platform, and services (such as reflection) which are common * has exactly one platform, and services (such as reflection) which are common
@ -236,11 +254,12 @@ export class PlatformRef_ extends PlatformRef {
}); });
app = new ApplicationRef_(this, zone, injector); app = new ApplicationRef_(this, zone, injector);
this._applications.push(app); this._applications.push(app);
_runAppInitializers(injector);
return app; return app;
} }
dispose(): void { dispose(): void {
this._applications.forEach((app) => app.dispose()); ListWrapper.clone(this._applications).forEach((app) => app.dispose());
this._disposeListeners.forEach((dispose) => dispose()); this._disposeListeners.forEach((dispose) => dispose());
this._dispose(); this._dispose();
} }
@ -249,6 +268,11 @@ export class PlatformRef_ extends PlatformRef {
_applicationDisposed(app: ApplicationRef): void { ListWrapper.remove(this._applications, app); } _applicationDisposed(app: ApplicationRef): void { ListWrapper.remove(this._applications, app); }
} }
function _runAppInitializers(injector: Injector): void {
let inits: Function[] = injector.getOptional(APP_INITIALIZER);
if (isPresent(inits)) inits.forEach(init => init());
}
/** /**
* A reference to an Angular application running on a page. * A reference to an Angular application running on a page.
* *
@ -439,7 +463,7 @@ export class ApplicationRef_ extends ApplicationRef {
dispose(): void { dispose(): void {
// TODO(alxhub): Dispose of the NgZone. // TODO(alxhub): Dispose of the NgZone.
this._rootComponents.forEach((ref) => ref.dispose()); ListWrapper.clone(this._rootComponents).forEach((ref) => ref.dispose());
this._disposeListeners.forEach((dispose) => dispose()); this._disposeListeners.forEach((dispose) => dispose());
this._platform._applicationDisposed(this); this._platform._applicationDisposed(this);
} }

View File

@ -48,3 +48,14 @@ export const APP_ID_RANDOM_PROVIDER: Provider =
function _randomChar(): string { function _randomChar(): string {
return StringWrapper.fromCharCode(97 + Math.floor(Math.random() * 25)); return StringWrapper.fromCharCode(97 + Math.floor(Math.random() * 25));
} }
/**
* A function that will be executed when a platform is initialized.
*/
export const PLATFORM_INITIALIZER: OpaqueToken =
CONST_EXPR(new OpaqueToken("Platform Initializer"));
/**
* A function that will be executed when an application is initialized.
*/
export const APP_INITIALIZER: OpaqueToken = CONST_EXPR(new OpaqueToken("Application Initializer"));

View File

@ -11,13 +11,13 @@ import {
reflector, reflector,
APPLICATION_COMMON_PROVIDERS, APPLICATION_COMMON_PROVIDERS,
PLATFORM_COMMON_PROVIDERS, PLATFORM_COMMON_PROVIDERS,
EVENT_MANAGER_PLUGINS EVENT_MANAGER_PLUGINS,
PLATFORM_INITIALIZER
} from "angular2/core"; } from "angular2/core";
import {COMMON_DIRECTIVES, COMMON_PIPES, FORM_PROVIDERS} from "angular2/common"; import {COMMON_DIRECTIVES, COMMON_PIPES, FORM_PROVIDERS} from "angular2/common";
import {Renderer} from 'angular2/render'; import {Renderer} from 'angular2/render';
import {Testability} from 'angular2/src/core/testability/testability'; import {Testability} from 'angular2/src/core/testability/testability';
// TODO change these imports once dom_adapter is moved out of core
import {DOM} from 'angular2/src/core/dom/dom_adapter'; import {DOM} from 'angular2/src/core/dom/dom_adapter';
import {DomEventsPlugin} from 'angular2/src/platform/dom/events/dom_events'; import {DomEventsPlugin} from 'angular2/src/platform/dom/events/dom_events';
import {KeyEventsPlugin} from 'angular2/src/platform/dom/events/key_events'; import {KeyEventsPlugin} from 'angular2/src/platform/dom/events/key_events';
@ -42,8 +42,10 @@ export {
export {By} from 'angular2/src/platform/browser/debug/by'; export {By} from 'angular2/src/platform/browser/debug/by';
export {BrowserDomAdapter} from './browser/browser_adapter'; export {BrowserDomAdapter} from './browser/browser_adapter';
export const BROWSER_PROVIDERS: Array<any /*Type | Provider | any[]*/> = export const BROWSER_PROVIDERS: Array<any /*Type | Provider | any[]*/> = CONST_EXPR([
CONST_EXPR([PLATFORM_COMMON_PROVIDERS]); PLATFORM_COMMON_PROVIDERS,
new Provider(PLATFORM_INITIALIZER, {useValue: initDomAdapter, multi: true}),
]);
function _exceptionHandler(): ExceptionHandler { function _exceptionHandler(): ExceptionHandler {
return new ExceptionHandler(DOM, false); return new ExceptionHandler(DOM, false);
@ -73,7 +75,6 @@ export const BROWSER_APP_COMMON_PROVIDERS: Array<any /*Type | Provider | any[]*/
]); ]);
export function initDomAdapter() { export function initDomAdapter() {
// TODO: refactor into a generic init function
BrowserDomAdapter.makeCurrent(); BrowserDomAdapter.makeCurrent();
wtfInit(); wtfInit();
BrowserGetTestability.init(); BrowserGetTestability.init();

View File

@ -1,6 +1,7 @@
import { import {
AsyncTestCompleter, AsyncTestCompleter,
beforeEach, beforeEach,
afterEach,
ddescribe, ddescribe,
describe, describe,
expect, expect,
@ -8,6 +9,7 @@ import {
inject, inject,
it, it,
xdescribe, xdescribe,
Log,
xit xit
} from 'angular2/testing_internal'; } from 'angular2/testing_internal';
import {IS_DART, isPresent, stringify} from 'angular2/src/facade/lang'; import {IS_DART, isPresent, stringify} from 'angular2/src/facade/lang';
@ -18,7 +20,8 @@ import {BROWSER_PROVIDERS, BROWSER_APP_PROVIDERS} from 'angular2/platform/browse
import {DOM} from 'angular2/src/core/dom/dom_adapter'; import {DOM} from 'angular2/src/core/dom/dom_adapter';
import {DOCUMENT} from 'angular2/src/platform/dom/dom_tokens'; import {DOCUMENT} from 'angular2/src/platform/dom/dom_tokens';
import {PromiseWrapper} from 'angular2/src/facade/async'; import {PromiseWrapper} from 'angular2/src/facade/async';
import {provide, Inject, Injector} from 'angular2/core'; import {provide, Inject, Injector, PLATFORM_INITIALIZER, APP_INITIALIZER} from 'angular2/core';
import {disposePlatform} from 'angular2/src/core/application_ref';
import {ExceptionHandler} from 'angular2/src/facade/exceptions'; import {ExceptionHandler} from 'angular2/src/facade/exceptions';
import {Testability, TestabilityRegistry} from 'angular2/src/core/testability/testability'; import {Testability, TestabilityRegistry} from 'angular2/src/core/testability/testability';
import {ComponentRef_} from "angular2/src/core/linker/dynamic_component_loader"; import {ComponentRef_} from "angular2/src/core/linker/dynamic_component_loader";
@ -101,6 +104,8 @@ export function main() {
testProviders = [provide(DOCUMENT, {useValue: fakeDoc})]; testProviders = [provide(DOCUMENT, {useValue: fakeDoc})];
}); });
afterEach(disposePlatform);
it('should throw if bootstrapped Directive is not a Component', it('should throw if bootstrapped Directive is not a Component',
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
var logger = new _ArrayLogger(); var logger = new _ArrayLogger();
@ -213,6 +218,23 @@ export function main() {
}); });
})); }));
it("should run platform initializers", inject([Log], (log: Log) => {
let p = platform([
BROWSER_PROVIDERS,
provide(PLATFORM_INITIALIZER, {useValue: log.fn("platform_init1"), multi: true}),
provide(PLATFORM_INITIALIZER, {useValue: log.fn("platform_init2"), multi: true})
]);
expect(log.result()).toEqual("platform_init1; platform_init2");
log.clear();
p.application([
BROWSER_APP_PROVIDERS,
provide(APP_INITIALIZER, {useValue: log.fn("app_init1"), multi: true}),
provide(APP_INITIALIZER, {useValue: log.fn("app_init2"), multi: true})
]);
expect(log.result()).toEqual("app_init1; app_init2");
}));
it('should register each application with the testability registry', it('should register each application with the testability registry',
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
var refPromise1 = bootstrap(HelloRootCmp, testProviders); var refPromise1 = bootstrap(HelloRootCmp, testProviders);

View File

@ -27,6 +27,7 @@ import {SymbolsDiff} from './symbol_inspector/symbol_differ';
var NG_ALL = [ var NG_ALL = [
'APP_COMPONENT', 'APP_COMPONENT',
'APP_INITIALIZER',
'APP_ID', 'APP_ID',
'AbstractProviderError', 'AbstractProviderError',
'AbstractProviderError.addKey()', 'AbstractProviderError.addKey()',
@ -1396,6 +1397,7 @@ var NG_ALL = [
'resolveForwardRef():js', 'resolveForwardRef():js',
'wtfCreateScope():js', 'wtfCreateScope():js',
'PLATFORM_COMMON_PROVIDERS', 'PLATFORM_COMMON_PROVIDERS',
'PLATFORM_INITIALIZER',
'wtfCreateScope:dart', 'wtfCreateScope:dart',
'wtfEndTimeRange():js', 'wtfEndTimeRange():js',
'wtfEndTimeRange:dart', 'wtfEndTimeRange:dart',