feat(core): Add a long-form syntax for Angular bootstrapping.
This change adds a syntax for bootstrapping Angular on a page that allows more fine-grained control of the hierarchy created. platform() creates a platform injector (of which there can only be one). From the platform, .application() creates an Angular application including a Zone and all specified application bindings (e.g. for the DOM, HTTP, Compiler, Renderer, etc). At the application level, .bootstrap() will bootstrap the given component into that application. Closes #3852
This commit is contained in:
@ -1,4 +1,3 @@
|
||||
import {DEFAULT_PIPES} from 'angular2/src/core/pipes';
|
||||
import {FORM_BINDINGS} from 'angular2/src/core/forms';
|
||||
import {bind, Binding, Injector, OpaqueToken} from 'angular2/src/core/di';
|
||||
import {
|
||||
@ -10,37 +9,12 @@ import {
|
||||
print,
|
||||
stringify
|
||||
} from 'angular2/src/core/facade/lang';
|
||||
import {
|
||||
BaseException,
|
||||
WrappedException,
|
||||
ExceptionHandler
|
||||
} from 'angular2/src/core/facade/exceptions';
|
||||
import {BrowserDomAdapter} from 'angular2/src/core/dom/browser_adapter';
|
||||
import {BrowserGetTestability} from 'angular2/src/core/testability/browser_testability';
|
||||
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
||||
import {Compiler, CompilerCache} from './compiler/compiler';
|
||||
import {Reflector, reflector} from 'angular2/src/core/reflection/reflection';
|
||||
import {
|
||||
Parser,
|
||||
Lexer,
|
||||
ChangeDetection,
|
||||
DynamicChangeDetection,
|
||||
JitChangeDetection,
|
||||
PreGeneratedChangeDetection,
|
||||
IterableDiffers,
|
||||
defaultIterableDiffers,
|
||||
KeyValueDiffers,
|
||||
defaultKeyValueDiffers
|
||||
} from 'angular2/src/core/change_detection/change_detection';
|
||||
import {ViewLoader} from 'angular2/src/core/render/dom/compiler/view_loader';
|
||||
import {StyleUrlResolver} from 'angular2/src/core/render/dom/compiler/style_url_resolver';
|
||||
import {StyleInliner} from 'angular2/src/core/render/dom/compiler/style_inliner';
|
||||
import {ViewResolver} from './compiler/view_resolver';
|
||||
import {DirectiveResolver} from './compiler/directive_resolver';
|
||||
import {PipeResolver} from './compiler/pipe_resolver';
|
||||
import {ListWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {Promise, PromiseWrapper, PromiseCompleter} from 'angular2/src/core/facade/async';
|
||||
import {NgZone} from 'angular2/src/core/zone/ng_zone';
|
||||
import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle';
|
||||
import {XHR} from 'angular2/src/core/render/xhr';
|
||||
import {XHRImpl} from 'angular2/src/core/render/xhr_impl';
|
||||
import {
|
||||
@ -50,8 +24,6 @@ import {
|
||||
} from 'angular2/src/core/render/dom/events/event_manager';
|
||||
import {KeyEventsPlugin} from 'angular2/src/core/render/dom/events/key_events';
|
||||
import {HammerGesturesPlugin} from 'angular2/src/core/render/dom/events/hammer_gestures';
|
||||
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
|
||||
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
||||
import {AppRootUrl} from 'angular2/src/core/services/app_root_url';
|
||||
import {AnchorBasedAppRootUrl} from 'angular2/src/core/services/anchor_based_app_root_url';
|
||||
import {
|
||||
@ -59,11 +31,6 @@ import {
|
||||
DynamicComponentLoader
|
||||
} from 'angular2/src/core/compiler/dynamic_component_loader';
|
||||
import {TestabilityRegistry, Testability} from 'angular2/src/core/testability/testability';
|
||||
import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool';
|
||||
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
|
||||
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
|
||||
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
|
||||
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
|
||||
import {Renderer, RenderCompiler} from 'angular2/src/core/render/api';
|
||||
import {
|
||||
DomRenderer,
|
||||
@ -81,46 +48,23 @@ import {
|
||||
SharedStylesHost,
|
||||
DomSharedStylesHost
|
||||
} from 'angular2/src/core/render/dom/view/shared_styles_host';
|
||||
import {internalView} from 'angular2/src/core/compiler/view_ref';
|
||||
import {APP_COMPONENT_REF_PROMISE, APP_COMPONENT} from './application_tokens';
|
||||
import {wtfInit} from './profile/wtf_init';
|
||||
import {EXCEPTION_BINDING} from './platform_bindings';
|
||||
import {ApplicationRef} from './application_ref';
|
||||
import {AnimationBuilder} from 'angular2/src/animate/animation_builder';
|
||||
import {BrowserDetails} from 'angular2/src/animate/browser_details';
|
||||
import {wtfInit} from './profile/wtf_init';
|
||||
import {platformCommon, PlatformRef, applicationCommonBindings} from './application_ref';
|
||||
|
||||
var _rootInjector: Injector;
|
||||
|
||||
// Contains everything that is safe to share between applications.
|
||||
var _rootBindings = [bind(Reflector).toValue(reflector), TestabilityRegistry];
|
||||
|
||||
function _injectorBindings(appComponentType): Array<Type | Binding | any[]> {
|
||||
var bestChangeDetection = new DynamicChangeDetection();
|
||||
if (PreGeneratedChangeDetection.isSupported()) {
|
||||
bestChangeDetection = new PreGeneratedChangeDetection();
|
||||
} else if (JitChangeDetection.isSupported()) {
|
||||
bestChangeDetection = new JitChangeDetection();
|
||||
/**
|
||||
* A default set of bindings which apply only to an Angular application running on
|
||||
* the UI thread.
|
||||
*/
|
||||
export function applicationDomBindings(): Array<Type | Binding | any[]> {
|
||||
if (isBlank(DOM)) {
|
||||
throw "Must set a root DOM adapter first.";
|
||||
}
|
||||
return [
|
||||
bind(DOCUMENT)
|
||||
.toValue(DOM.defaultDoc()),
|
||||
bind(APP_COMPONENT).toValue(appComponentType),
|
||||
bind(APP_COMPONENT_REF_PROMISE)
|
||||
.toFactory(
|
||||
(dynamicComponentLoader, injector, testability, registry) => {
|
||||
// TODO(rado): investigate whether to support bindings on root component.
|
||||
return dynamicComponentLoader.loadAsRoot(appComponentType, null, injector)
|
||||
.then((componentRef) => {
|
||||
registry.registerApplication(componentRef.location.nativeElement, testability);
|
||||
return componentRef;
|
||||
});
|
||||
},
|
||||
[DynamicComponentLoader, Injector, Testability, TestabilityRegistry]),
|
||||
|
||||
bind(appComponentType)
|
||||
.toFactory((p: Promise<any>) => p.then(ref => ref.instance), [APP_COMPONENT_REF_PROMISE]),
|
||||
bind(LifeCycle).toFactory((exceptionHandler) => new LifeCycle(null, assertionsEnabled()),
|
||||
[ExceptionHandler]),
|
||||
EventManager,
|
||||
new Binding(EVENT_MANAGER_PLUGINS, {toClass: DomEventsPlugin, multi: true}),
|
||||
new Binding(EVENT_MANAGER_PLUGINS, {toClass: KeyEventsPlugin, multi: true}),
|
||||
@ -135,31 +79,10 @@ function _injectorBindings(appComponentType): Array<Type | Binding | any[]> {
|
||||
bind(RenderCompiler).toAlias(DefaultDomCompiler),
|
||||
DomSharedStylesHost,
|
||||
bind(SharedStylesHost).toAlias(DomSharedStylesHost),
|
||||
ProtoViewFactory,
|
||||
AppViewPool,
|
||||
bind(APP_VIEW_POOL_CAPACITY).toValue(10000),
|
||||
AppViewManager,
|
||||
AppViewManagerUtils,
|
||||
AppViewListener,
|
||||
Compiler,
|
||||
CompilerCache,
|
||||
ViewResolver,
|
||||
DEFAULT_PIPES,
|
||||
bind(IterableDiffers).toValue(defaultIterableDiffers),
|
||||
bind(KeyValueDiffers).toValue(defaultKeyValueDiffers),
|
||||
bind(ChangeDetection).toValue(bestChangeDetection),
|
||||
ViewLoader,
|
||||
DirectiveResolver,
|
||||
PipeResolver,
|
||||
Parser,
|
||||
Lexer,
|
||||
EXCEPTION_BINDING,
|
||||
bind(XHR).toValue(new XHRImpl()),
|
||||
ComponentUrlMapper,
|
||||
UrlResolver,
|
||||
StyleUrlResolver,
|
||||
StyleInliner,
|
||||
DynamicComponentLoader,
|
||||
Testability,
|
||||
AnchorBasedAppRootUrl,
|
||||
bind(AppRootUrl).toAlias(AnchorBasedAppRootUrl),
|
||||
@ -169,16 +92,28 @@ function _injectorBindings(appComponentType): Array<Type | Binding | any[]> {
|
||||
];
|
||||
}
|
||||
|
||||
export function createNgZone(): NgZone {
|
||||
return new NgZone({enableLongStackTrace: assertionsEnabled()});
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the Angular context on the page.
|
||||
*
|
||||
* If no bindings are provided, calling {@link platform}() is idempotent,
|
||||
* and will use the default platform bindings (which can be obtained from
|
||||
* {@link rootBindings}).
|
||||
*/
|
||||
export function platform(bindings?: Array<Type | Binding | any[]>): PlatformRef {
|
||||
return platformCommon(bindings, () => {
|
||||
BrowserDomAdapter.makeCurrent();
|
||||
wtfInit();
|
||||
BrowserGetTestability.init();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrapping for Angular applications.
|
||||
*
|
||||
* You instantiate an Angular application by explicitly specifying a component to use as the root
|
||||
* component for your
|
||||
* application via the `bootstrap()` method.
|
||||
* You instantiate an Angular application by explicitly specifying a component to use
|
||||
* as the root component for your application via the `bootstrap()` method.
|
||||
*
|
||||
* ## Simple Example
|
||||
*
|
||||
@ -193,15 +128,12 @@ export function createNgZone(): NgZone {
|
||||
* </html>
|
||||
* ```
|
||||
*
|
||||
* An application is bootstrapped inside an existing browser DOM, typically `index.html`. Unlike
|
||||
* Angular 1, Angular 2
|
||||
* does not compile/process bindings in `index.html`. This is mainly for security reasons, as well
|
||||
* as architectural
|
||||
* changes in Angular 2. This means that `index.html` can safely be processed using server-side
|
||||
* technologies such as
|
||||
* bindings. Bindings can thus use double-curly `{{ syntax }}` without collision from Angular 2
|
||||
* component double-curly
|
||||
* `{{ syntax }}`.
|
||||
* An application is bootstrapped inside an existing browser DOM, typically `index.html`.
|
||||
* Unlike Angular 1, Angular 2 does not compile/process bindings in `index.html`. This is
|
||||
* mainly for security reasons, as well as architectural changes in Angular 2. This means
|
||||
* that `index.html` can safely be processed using server-side technologies such as
|
||||
* bindings. Bindings can thus use double-curly `{{ syntax }}` without collision from
|
||||
* Angular 2 component double-curly `{{ syntax }}`.
|
||||
*
|
||||
* We can use this script code:
|
||||
*
|
||||
@ -225,134 +157,77 @@ export function createNgZone(): NgZone {
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* When the app developer invokes `bootstrap()` with the root component `MyApp` as its argument,
|
||||
* Angular performs the
|
||||
* following tasks:
|
||||
* When the app developer invokes `bootstrap()` with the root component `MyApp` as its
|
||||
* argument, Angular performs the following tasks:
|
||||
*
|
||||
* 1. It uses the component's `selector` property to locate the DOM element which needs to be
|
||||
* upgraded into
|
||||
* the angular component.
|
||||
* 2. It creates a new child injector (from the platform injector). Optionally, you can also
|
||||
* override the injector configuration for an app by
|
||||
* invoking `bootstrap` with the `componentInjectableBindings` argument.
|
||||
* 3. It creates a new `Zone` and connects it to the angular application's change detection domain
|
||||
* instance.
|
||||
* 4. It creates a shadow DOM on the selected component's host element and loads the template into
|
||||
* it.
|
||||
* 1. It uses the component's `selector` property to locate the DOM element which needs
|
||||
* to be upgraded into the angular component.
|
||||
* 2. It creates a new child injector (from the platform injector). Optionally, you can
|
||||
* also override the injector configuration for an app by invoking `bootstrap` with the
|
||||
* `componentInjectableBindings` argument.
|
||||
* 3. It creates a new `Zone` and connects it to the angular application's change detection
|
||||
* domain instance.
|
||||
* 4. It creates a shadow DOM on the selected component's host element and loads the
|
||||
* template into it.
|
||||
* 5. It instantiates the specified component.
|
||||
* 6. Finally, Angular performs change detection to apply the initial data bindings for the
|
||||
* application.
|
||||
* application.
|
||||
*
|
||||
*
|
||||
* ## Instantiating Multiple Applications on a Single Page
|
||||
*
|
||||
* There are two ways to do this.
|
||||
*
|
||||
*
|
||||
* ### Isolated Applications
|
||||
*
|
||||
* Angular creates a new application each time that the `bootstrap()` method is invoked. When
|
||||
* multiple applications
|
||||
* are created for a page, Angular treats each application as independent within an isolated change
|
||||
* detection and
|
||||
* `Zone` domain. If you need to share data between applications, use the strategy described in the
|
||||
* next
|
||||
* section, "Applications That Share Change Detection."
|
||||
* Angular creates a new application each time that the `bootstrap()` method is invoked.
|
||||
* When multiple applications are created for a page, Angular treats each application as
|
||||
* independent within an isolated change detection and `Zone` domain. If you need to share
|
||||
* data between applications, use the strategy described in the next section, "Applications
|
||||
* That Share Change Detection."
|
||||
*
|
||||
*
|
||||
* ### Applications That Share Change Detection
|
||||
*
|
||||
* If you need to bootstrap multiple applications that share common data, the applications must
|
||||
* share a common
|
||||
* change detection and zone. To do that, create a meta-component that lists the application
|
||||
* components in its template.
|
||||
* By only invoking the `bootstrap()` method once, with the meta-component as its argument, you
|
||||
* ensure that only a
|
||||
* single change detection zone is created and therefore data can be shared across the applications.
|
||||
* If you need to bootstrap multiple applications that share common data, the applications
|
||||
* must share a common change detection and zone. To do that, create a meta-component that
|
||||
* lists the application components in its template.
|
||||
*
|
||||
* By only invoking the `bootstrap()` method once, with the meta-component as its argument,
|
||||
* you ensure that only a single change detection zone is created and therefore data can be
|
||||
* shared across the applications.
|
||||
*
|
||||
*
|
||||
* ## Platform Injector
|
||||
*
|
||||
* When working within a browser window, there are many singleton resources: cookies, title,
|
||||
* location, and others.
|
||||
* Angular services that represent these resources must likewise be shared across all Angular
|
||||
* applications that
|
||||
* occupy the same browser window. For this reason, Angular creates exactly one global platform
|
||||
* injector which stores
|
||||
* all shared services, and each angular application injector has the platform injector as its
|
||||
* parent.
|
||||
* location, and others. Angular services that represent these resources must likewise be
|
||||
* shared across all Angular applications that occupy the same browser window. For this
|
||||
* reason, Angular creates exactly one global platform injector which stores all shared
|
||||
* services, and each angular application injector has the platform injector as its parent.
|
||||
*
|
||||
* Each application has its own private injector as well. When there are multiple applications on a
|
||||
* page, Angular treats
|
||||
* each application injector's services as private to that application.
|
||||
* Each application has its own private injector as well. When there are multipl
|
||||
* applications on a page, Angular treats each application injector's services as private
|
||||
* to that application.
|
||||
*
|
||||
*
|
||||
* # API
|
||||
* - `appComponentType`: The root component which should act as the application. This is a reference
|
||||
* to a `Type`
|
||||
* which is annotated with `@Component(...)`.
|
||||
* - `componentInjectableBindings`: An additional set of bindings that can be added to the app
|
||||
* injector
|
||||
* to override default injection behavior.
|
||||
* - `errorReporter`: `function(exception:any, stackTrace:string)` a default error reporter for
|
||||
* unhandled exceptions.
|
||||
* - `appComponentType`: The root component which should act as the application. This is
|
||||
* a reference to a `Type` which is annotated with `@Component(...)`.
|
||||
* - `componentInjectableBindings`: An additional set of bindings that can be added to the
|
||||
* app injector to override default injection behavior.
|
||||
* - `errorReporter`: `function(exception:any, stackTrace:string)` a default error reporter
|
||||
* for unhandled exceptions.
|
||||
*
|
||||
* Returns a `Promise` of {@link ApplicationRef}.
|
||||
*/
|
||||
export function commonBootstrap(appComponentType: /*Type*/ any,
|
||||
componentInjectableBindings: Array<Type | Binding | any[]> = null):
|
||||
Promise<ApplicationRef> {
|
||||
BrowserDomAdapter.makeCurrent();
|
||||
wtfInit();
|
||||
var bootstrapProcess = PromiseWrapper.completer();
|
||||
var zone = createNgZone();
|
||||
|
||||
zone.run(() => {
|
||||
var exceptionHandler;
|
||||
|
||||
try {
|
||||
var appInjector = _createAppInjector(appComponentType, componentInjectableBindings, zone);
|
||||
exceptionHandler = appInjector.get(ExceptionHandler);
|
||||
zone.overrideOnErrorHandler((e, s) => exceptionHandler.call(e, s));
|
||||
|
||||
var compRefToken: Promise<any> = appInjector.get(APP_COMPONENT_REF_PROMISE);
|
||||
var tick = (componentRef) => {
|
||||
var appChangeDetector = internalView(componentRef.hostView).changeDetector;
|
||||
// retrieve life cycle: may have already been created if injected in root component
|
||||
var lc = appInjector.get(LifeCycle);
|
||||
lc.registerWith(zone, appChangeDetector);
|
||||
lc.tick(); // the first tick that will bootstrap the app
|
||||
|
||||
bootstrapProcess.resolve(new ApplicationRef(componentRef, appComponentType, appInjector));
|
||||
};
|
||||
|
||||
var tickResult = PromiseWrapper.then(compRefToken, tick);
|
||||
|
||||
PromiseWrapper.then(tickResult,
|
||||
(_) => {}); // required for Dart to trigger the default error handler
|
||||
PromiseWrapper.then(tickResult, null,
|
||||
(err, stackTrace) => { bootstrapProcess.reject(err, stackTrace); });
|
||||
} catch (e) {
|
||||
if (isPresent(exceptionHandler)) {
|
||||
exceptionHandler.call(e, e.stack);
|
||||
} else {
|
||||
// The error happened during the creation of an injector, most likely because of a bug in
|
||||
// DI.
|
||||
// We cannot use the provided exception handler, so we default to writing to the DOM.
|
||||
DOM.logError(e);
|
||||
}
|
||||
bootstrapProcess.reject(e, e.stack);
|
||||
}
|
||||
});
|
||||
return bootstrapProcess.promise;
|
||||
}
|
||||
|
||||
function _createAppInjector(appComponentType: Type, bindings: Array<Type | Binding | any[]>,
|
||||
zone: NgZone): Injector {
|
||||
if (isBlank(_rootInjector)) _rootInjector = Injector.resolveAndCreate(_rootBindings);
|
||||
var mergedBindings: any[] =
|
||||
isPresent(bindings) ? ListWrapper.concat(_injectorBindings(appComponentType), bindings) :
|
||||
_injectorBindings(appComponentType);
|
||||
mergedBindings.push(bind(NgZone).toValue(zone));
|
||||
return _rootInjector.resolveAndCreateChild(mergedBindings);
|
||||
appBindings: Array<Type | Binding | any[]> = null):
|
||||
Promise<ComponentRef> {
|
||||
var p = platform();
|
||||
var bindings = [applicationCommonBindings(), applicationDomBindings()];
|
||||
if (isPresent(appBindings)) {
|
||||
bindings.push(appBindings);
|
||||
}
|
||||
return p.application(bindings).bootstrap(appComponentType);
|
||||
}
|
||||
|
Reference in New Issue
Block a user