refactor(router): use ApplicationRef to provide the first bootstrapped component as ROUTER_PRIMARY_COMPONENT automatically.

This removes the routerBindings function as it is no longer necessary. ROUTER_BINDINGS will automatically pick the first bootstrapped component to satisfy ROUTER_PRIMARY_COMPONENT.

BREAKING CHANGE:

Before: bootstrap(MyComponent, [routerBindings(myComponent)]);
After: bootstrap(MyComponent, [ROUTER_BINDINGS]);

Closes #4643
This commit is contained in:
Alex Rickabaugh 2015-10-09 16:22:07 -07:00
parent 19c1bd7375
commit 90191ce261
9 changed files with 57 additions and 89 deletions

View File

@ -29,15 +29,15 @@ import {RouterLink} from './src/router/router_link';
import {RouteRegistry} from './src/router/route_registry'; import {RouteRegistry} from './src/router/route_registry';
import {Location} from './src/router/location'; import {Location} from './src/router/location';
import {bind, OpaqueToken, Binding} from './core'; import {bind, OpaqueToken, Binding} from './core';
import {CONST_EXPR, Type} from './src/core/facade/lang'; import {CONST_EXPR} from './src/core/facade/lang';
import {ApplicationRef} from './src/core/application_ref';
import {BaseException} from 'angular2/src/core/facade/exceptions';
/** /**
* Token used to bind the component with the top-level {@link RouteConfig}s for the * Token used to bind the component with the top-level {@link RouteConfig}s for the
* application. * application.
* *
* You can use the {@link routerBindings} function in your {@link bootstrap} bindings to
* simplify setting up these bindings.
*
* ## Example ([live demo](http://plnkr.co/edit/iRUP8B5OUbxCWQ3AcIDm)) * ## Example ([live demo](http://plnkr.co/edit/iRUP8B5OUbxCWQ3AcIDm))
* *
* ``` * ```
@ -45,7 +45,6 @@ import {CONST_EXPR, Type} from './src/core/facade/lang';
* import { * import {
* ROUTER_DIRECTIVES, * ROUTER_DIRECTIVES,
* ROUTER_BINDINGS, * ROUTER_BINDINGS,
* ROUTER_PRIMARY_COMPONENT,
* RouteConfig * RouteConfig
* } from 'angular2/router'; * } from 'angular2/router';
* *
@ -58,10 +57,7 @@ import {CONST_EXPR, Type} from './src/core/facade/lang';
* // ... * // ...
* } * }
* *
* bootstrap(AppCmp, [ * bootstrap(AppCmp, [ROUTER_BINDINGS]);
* ROUTER_BINDINGS,
* bind(ROUTER_PRIMARY_COMPONENT).toValue(AppCmp)
* ]);
* ``` * ```
*/ */
export const ROUTER_PRIMARY_COMPONENT: OpaqueToken = export const ROUTER_PRIMARY_COMPONENT: OpaqueToken =
@ -76,7 +72,7 @@ export const ROUTER_PRIMARY_COMPONENT: OpaqueToken =
* *
* ``` * ```
* import {Component, View} from 'angular2/angular2'; * import {Component, View} from 'angular2/angular2';
* import {ROUTER_DIRECTIVES, routerBindings, RouteConfig} from 'angular2/router'; * import {ROUTER_DIRECTIVES, ROUTER_BINDINGS, RouteConfig} from 'angular2/router';
* *
* @Component({...}) * @Component({...})
* @View({directives: [ROUTER_DIRECTIVES]}) * @View({directives: [ROUTER_DIRECTIVES]})
@ -87,7 +83,7 @@ export const ROUTER_PRIMARY_COMPONENT: OpaqueToken =
* // ... * // ...
* } * }
* *
* bootstrap(AppCmp, [routerBindings(AppCmp)]); * bootstrap(AppCmp, ROUTER_BINDINGS);
* ``` * ```
*/ */
export const ROUTER_DIRECTIVES: any[] = CONST_EXPR([RouterOutlet, RouterLink]); export const ROUTER_DIRECTIVES: any[] = CONST_EXPR([RouterOutlet, RouterLink]);
@ -95,11 +91,6 @@ export const ROUTER_DIRECTIVES: any[] = CONST_EXPR([RouterOutlet, RouterLink]);
/** /**
* A list of {@link Binding}s. To use the router, you must add this to your application. * A list of {@link Binding}s. To use the router, you must add this to your application.
* *
* Note that you also need to bind to {@link ROUTER_PRIMARY_COMPONENT}.
*
* You can use the {@link routerBindings} function in your {@link bootstrap} bindings to
* simplify setting up these bindings.
*
* ## Example ([live demo](http://plnkr.co/edit/iRUP8B5OUbxCWQ3AcIDm)) * ## Example ([live demo](http://plnkr.co/edit/iRUP8B5OUbxCWQ3AcIDm))
* *
* ``` * ```
@ -107,7 +98,6 @@ export const ROUTER_DIRECTIVES: any[] = CONST_EXPR([RouterOutlet, RouterLink]);
* import { * import {
* ROUTER_DIRECTIVES, * ROUTER_DIRECTIVES,
* ROUTER_BINDINGS, * ROUTER_BINDINGS,
* ROUTER_PRIMARY_COMPONENT,
* RouteConfig * RouteConfig
* } from 'angular2/router'; * } from 'angular2/router';
* *
@ -120,50 +110,30 @@ export const ROUTER_DIRECTIVES: any[] = CONST_EXPR([RouterOutlet, RouterLink]);
* // ... * // ...
* } * }
* *
* bootstrap(AppCmp, [ * bootstrap(AppCmp, [ROUTER_BINDINGS]);
* ROUTER_BINDINGS,
* bind(ROUTER_PRIMARY_COMPONENT).toValue(AppCmp)
* ]);
* ``` * ```
*/ */
export const ROUTER_BINDINGS: any[] = CONST_EXPR([ export const ROUTER_BINDINGS: any[] = CONST_EXPR([
RouteRegistry, RouteRegistry,
CONST_EXPR(new Binding(LocationStrategy, {toClass: PathLocationStrategy})), CONST_EXPR(new Binding(LocationStrategy, {toClass: PathLocationStrategy})),
Location, Location,
CONST_EXPR( CONST_EXPR(new Binding(Router,
new Binding(Router, {
{ toFactory: routerFactory,
toFactory: routerFactory, deps: CONST_EXPR([RouteRegistry, Location, ROUTER_PRIMARY_COMPONENT])
deps: CONST_EXPR([RouteRegistry, Location, ROUTER_PRIMARY_COMPONENT]) })),
})) CONST_EXPR(new Binding(
ROUTER_PRIMARY_COMPONENT,
{toFactory: routerPrimaryComponentFactory, deps: CONST_EXPR([ApplicationRef])}))
]); ]);
function routerFactory(registry, location, primaryComponent) { function routerFactory(registry, location, primaryComponent) {
return new RootRouter(registry, location, primaryComponent); return new RootRouter(registry, location, primaryComponent);
} }
/** function routerPrimaryComponentFactory(app) {
* A list of {@link Binding}s. To use the router, you must add these bindings to if (app.componentTypes.length == 0) {
* your application. throw new BaseException("Bootstrap at least one component before injecting Router.");
* }
* ## Example ([live demo](http://plnkr.co/edit/iRUP8B5OUbxCWQ3AcIDm)) return app.componentTypes[0];
*
* ```
* import {Component, View} from 'angular2/angular2';
* import {ROUTER_DIRECTIVES, routerBindings, RouteConfig} from 'angular2/router';
*
* @Component({...})
* @View({directives: [ROUTER_DIRECTIVES]})
* @RouteConfig([
* {...},
* ])
* class AppCmp {
* // ...
* }
*
* bootstrap(AppCmp, [routerBindings(AppCmp)]);
* ```
*/
export function routerBindings(primaryComponent: Type): Array<any> {
return [ROUTER_BINDINGS, bind(ROUTER_PRIMARY_COMPONENT).toValue(primaryComponent)];
} }

View File

@ -230,9 +230,10 @@ export class PlatformRef_ extends PlatformRef {
private _initApp(zone: NgZone, bindings: Array<Type | Binding | any[]>): ApplicationRef { private _initApp(zone: NgZone, bindings: Array<Type | Binding | any[]>): ApplicationRef {
var injector: Injector; var injector: Injector;
var app: ApplicationRef;
zone.run(() => { zone.run(() => {
bindings.push(bind(NgZone).toValue(zone)); bindings.push(bind(NgZone).toValue(zone));
bindings.push(bind(ApplicationRef).toValue(this)); bindings.push(bind(ApplicationRef).toFactory((): ApplicationRef => app, []));
var exceptionHandler; var exceptionHandler;
try { try {
@ -247,7 +248,7 @@ export class PlatformRef_ extends PlatformRef {
} }
} }
}); });
var app = new ApplicationRef_(this, zone, injector); app = new ApplicationRef_(this, zone, injector);
this._applications.push(app); this._applications.push(app);
return app; return app;
} }
@ -312,11 +313,17 @@ export abstract class ApplicationRef {
* Dispose of this application and all of its components. * Dispose of this application and all of its components.
*/ */
abstract dispose(): void; abstract dispose(): void;
/**
* Get a list of component types registered to this application.
*/
get componentTypes(): Type[] { return unimplemented(); };
} }
export class ApplicationRef_ extends ApplicationRef { export class ApplicationRef_ extends ApplicationRef {
private _bootstrapListeners: Function[] = []; private _bootstrapListeners: Function[] = [];
private _rootComponents: ComponentRef[] = []; private _rootComponents: ComponentRef[] = [];
private _rootComponentTypes: Type[] = [];
constructor(private _platform: PlatformRef_, private _zone: NgZone, private _injector: Injector) { constructor(private _platform: PlatformRef_, private _zone: NgZone, private _injector: Injector) {
super(); super();
@ -334,6 +341,7 @@ export class ApplicationRef_ extends ApplicationRef {
componentBindings.push(bindings); componentBindings.push(bindings);
} }
var exceptionHandler = this._injector.get(ExceptionHandler); var exceptionHandler = this._injector.get(ExceptionHandler);
this._rootComponentTypes.push(componentType);
try { try {
var injector: Injector = this._injector.resolveAndCreateChild(componentBindings); var injector: Injector = this._injector.resolveAndCreateChild(componentBindings);
var compRefToken: Promise<ComponentRef> = injector.get(APP_COMPONENT_REF_PROMISE); var compRefToken: Promise<ComponentRef> = injector.get(APP_COMPONENT_REF_PROMISE);
@ -369,4 +377,6 @@ export class ApplicationRef_ extends ApplicationRef {
this._rootComponents.forEach((ref) => ref.dispose()); this._rootComponents.forEach((ref) => ref.dispose());
this._platform._applicationDisposed(this); this._platform._applicationDisposed(this);
} }
get componentTypes(): any[] { return this._rootComponentTypes; }
} }

View File

@ -18,7 +18,7 @@ import {EventListener, History, Location} from 'angular2/src/core/facade/browser
* import {Component, View} from 'angular2/angular2'; * import {Component, View} from 'angular2/angular2';
* import { * import {
* ROUTER_DIRECTIVES, * ROUTER_DIRECTIVES,
* routerBindings, * ROUTER_BINDINGS,
* RouteConfig, * RouteConfig,
* Location * Location
* } from 'angular2/router'; * } from 'angular2/router';
@ -34,9 +34,7 @@ import {EventListener, History, Location} from 'angular2/src/core/facade/browser
* } * }
* } * }
* *
* bootstrap(AppCmp, [ * bootstrap(AppCmp, [ROUTER_BINDINGS]);
* routerBindings(AppCmp)
* ]);
* ``` * ```
*/ */
@Injectable() @Injectable()

View File

@ -16,7 +16,7 @@ import {Url} from './url_parser';
* *
* ``` * ```
* import {bootstrap, Component, View} from 'angular2/angular2'; * import {bootstrap, Component, View} from 'angular2/angular2';
* import {Router, ROUTER_DIRECTIVES, routerBindings, RouteConfig} from 'angular2/router'; * import {Router, ROUTER_DIRECTIVES, ROUTER_BINDINGS, RouteConfig} from 'angular2/router';
* *
* @Component({...}) * @Component({...})
* @View({directives: [ROUTER_DIRECTIVES]}) * @View({directives: [ROUTER_DIRECTIVES]})
@ -34,7 +34,7 @@ import {Url} from './url_parser';
* } * }
* } * }
* *
* bootstrap(AppCmp, routerBindings(AppCmp)); * bootstrap(AppCmp, ROUTER_BINDINGS);
* ``` * ```
*/ */
export class RouteParams { export class RouteParams {
@ -54,7 +54,7 @@ export class RouteParams {
* *
* ``` * ```
* import {bootstrap, Component, View} from 'angular2/angular2'; * import {bootstrap, Component, View} from 'angular2/angular2';
* import {Router, ROUTER_DIRECTIVES, routerBindings, RouteConfig} from 'angular2/router'; * import {Router, ROUTER_DIRECTIVES, ROUTER_BINDINGS, RouteConfig} from 'angular2/router';
* *
* @Component({...}) * @Component({...})
* @View({directives: [ROUTER_DIRECTIVES]}) * @View({directives: [ROUTER_DIRECTIVES]})
@ -68,7 +68,7 @@ export class RouteParams {
* } * }
* } * }
* *
* bootstrap(AppCmp, routerBindings(AppCmp)); * bootstrap(AppCmp, ROUTER_BINDINGS);
* ``` * ```
*/ */
export class Instruction { export class Instruction {

View File

@ -17,7 +17,7 @@ import {OpaqueToken, Injectable, Optional, Inject} from 'angular2/src/core/di';
* *
* ``` * ```
* import {Component, View} from 'angular2/angular2'; * import {Component, View} from 'angular2/angular2';
* import {ROUTER_DIRECTIVES, routerBindings, RouteConfig} from 'angular2/router'; * import {ROUTER_DIRECTIVES, ROUTER_BINDINGS, RouteConfig} from 'angular2/router';
* *
* @Component({...}) * @Component({...})
* @View({directives: [ROUTER_DIRECTIVES]}) * @View({directives: [ROUTER_DIRECTIVES]})
@ -29,7 +29,7 @@ import {OpaqueToken, Injectable, Optional, Inject} from 'angular2/src/core/di';
* } * }
* *
* bootstrap(AppCmp, [ * bootstrap(AppCmp, [
* routerBindings(AppCmp), * ROUTER_BINDINGS,
* PathLocationStrategy, * PathLocationStrategy,
* bind(APP_BASE_HREF).toValue('/my/app') * bind(APP_BASE_HREF).toValue('/my/app')
* ]); * ]);
@ -59,7 +59,7 @@ export const APP_BASE_HREF: OpaqueToken = CONST_EXPR(new OpaqueToken('appBaseHre
* import {Component, View} from 'angular2/angular2'; * import {Component, View} from 'angular2/angular2';
* import { * import {
* ROUTER_DIRECTIVES, * ROUTER_DIRECTIVES,
* routerBindings, * ROUTER_BINDINGS,
* RouteConfig, * RouteConfig,
* Location * Location
* } from 'angular2/router'; * } from 'angular2/router';
@ -75,7 +75,7 @@ export const APP_BASE_HREF: OpaqueToken = CONST_EXPR(new OpaqueToken('appBaseHre
* } * }
* } * }
* *
* bootstrap(AppCmp, [routerBindings(AppCmp)]); * bootstrap(AppCmp, [ROUTER_BINDINGS]);
* ``` * ```
*/ */
@Injectable() @Injectable()

View File

@ -10,7 +10,7 @@ import {LocationStrategy} from './location_strategy';
* browser's URL. * browser's URL.
* *
* `PathLocationStrategy` is the default binding for {@link LocationStrategy} * `PathLocationStrategy` is the default binding for {@link LocationStrategy}
* provided in {@link routerBindings} and {@link ROUTER_BINDINGS}. * provided in {@link ROUTER_BINDINGS}.
* *
* If you're using `PathLocationStrategy`, you must provide a binding for * If you're using `PathLocationStrategy`, you must provide a binding for
* {@link APP_BASE_HREF} to a string representing the URL prefix that should * {@link APP_BASE_HREF} to a string representing the URL prefix that should
@ -27,7 +27,7 @@ import {LocationStrategy} from './location_strategy';
* import { * import {
* APP_BASE_HREF * APP_BASE_HREF
* ROUTER_DIRECTIVES, * ROUTER_DIRECTIVES,
* routerBindings, * ROUTER_BINDINGS,
* RouteConfig, * RouteConfig,
* Location * Location
* } from 'angular2/router'; * } from 'angular2/router';
@ -44,7 +44,7 @@ import {LocationStrategy} from './location_strategy';
* } * }
* *
* bootstrap(AppCmp, [ * bootstrap(AppCmp, [
* routerBindings(AppCmp), // includes binding to PathLocationStrategy * ROUTER_BINDINGS, // includes binding to PathLocationStrategy
* bind(APP_BASE_HREF).toValue('/my/app') * bind(APP_BASE_HREF).toValue('/my/app')
* ]); * ]);
* ``` * ```

View File

@ -90,6 +90,7 @@ var NG_API = [
'ApplicationRef:js', 'ApplicationRef:js',
'ApplicationRef.injector:js', 'ApplicationRef.injector:js',
'ApplicationRef.zone:js', 'ApplicationRef.zone:js',
'ApplicationRef.componentTypes:js',
/* /*
Abstract methods Abstract methods
'ApplicationRef.bootstrap()', 'ApplicationRef.bootstrap()',

View File

@ -20,7 +20,6 @@ import {Type} from 'angular2/src/core/facade/lang';
import { import {
ROUTER_BINDINGS, ROUTER_BINDINGS,
ROUTER_PRIMARY_COMPONENT,
Router, Router,
RouteConfig, RouteConfig,
APP_BASE_HREF, APP_BASE_HREF,
@ -57,8 +56,7 @@ export function main() {
}); });
it('should bootstrap an app with a hierarchy', inject([AsyncTestCompleter], (async) => { it('should bootstrap an app with a hierarchy', inject([AsyncTestCompleter], (async) => {
bootstrap(HierarchyAppCmp, bootstrap(HierarchyAppCmp, testBindings)
[bind(ROUTER_PRIMARY_COMPONENT).toValue(HierarchyAppCmp), testBindings])
.then((applicationRef) => { .then((applicationRef) => {
var router = applicationRef.hostComponent.router; var router = applicationRef.hostComponent.router;
router.subscribe((_) => { router.subscribe((_) => {
@ -72,8 +70,7 @@ export function main() {
it('should work in an app with redirects', inject([AsyncTestCompleter], (async) => { it('should work in an app with redirects', inject([AsyncTestCompleter], (async) => {
bootstrap(RedirectAppCmp, bootstrap(RedirectAppCmp, testBindings)
[bind(ROUTER_PRIMARY_COMPONENT).toValue(RedirectAppCmp), testBindings])
.then((applicationRef) => { .then((applicationRef) => {
var router = applicationRef.hostComponent.router; var router = applicationRef.hostComponent.router;
router.subscribe((_) => { router.subscribe((_) => {
@ -87,7 +84,7 @@ export function main() {
it('should work in an app with async components', inject([AsyncTestCompleter], (async) => { it('should work in an app with async components', inject([AsyncTestCompleter], (async) => {
bootstrap(AsyncAppCmp, [bind(ROUTER_PRIMARY_COMPONENT).toValue(AsyncAppCmp), testBindings]) bootstrap(AsyncAppCmp, testBindings)
.then((applicationRef) => { .then((applicationRef) => {
var router = applicationRef.hostComponent.router; var router = applicationRef.hostComponent.router;
router.subscribe((_) => { router.subscribe((_) => {
@ -102,8 +99,7 @@ export function main() {
it('should work in an app with async components defined with "loader"', it('should work in an app with async components defined with "loader"',
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
bootstrap(ConciseAsyncAppCmp, bootstrap(ConciseAsyncAppCmp, testBindings)
[bind(ROUTER_PRIMARY_COMPONENT).toValue(AsyncAppCmp), testBindings])
.then((applicationRef) => { .then((applicationRef) => {
var router = applicationRef.hostComponent.router; var router = applicationRef.hostComponent.router;
router.subscribe((_) => { router.subscribe((_) => {
@ -118,9 +114,7 @@ export function main() {
it('should work in an app with a constructor component', it('should work in an app with a constructor component',
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
bootstrap( bootstrap(ExplicitConstructorAppCmp, testBindings)
ExplicitConstructorAppCmp,
[bind(ROUTER_PRIMARY_COMPONENT).toValue(ExplicitConstructorAppCmp), testBindings])
.then((applicationRef) => { .then((applicationRef) => {
var router = applicationRef.hostComponent.router; var router = applicationRef.hostComponent.router;
router.subscribe((_) => { router.subscribe((_) => {
@ -136,8 +130,7 @@ export function main() {
inject( inject(
[AsyncTestCompleter], [AsyncTestCompleter],
(async) => { (async) => {
bootstrap(WrongConfigCmp, bootstrap(WrongConfigCmp, testBindings)
[bind(ROUTER_PRIMARY_COMPONENT).toValue(WrongConfigCmp), testBindings])
.catch((e) => { .catch((e) => {
expect(e.originalException) expect(e.originalException)
.toContainError( .toContainError(
@ -150,9 +143,7 @@ export function main() {
inject( inject(
[AsyncTestCompleter], [AsyncTestCompleter],
(async) => { (async) => {
bootstrap( bootstrap(WrongComponentTypeCmp, testBindings)
WrongComponentTypeCmp,
[bind(ROUTER_PRIMARY_COMPONENT).toValue(WrongComponentTypeCmp), testBindings])
.catch((e) => { .catch((e) => {
expect(e.originalException) expect(e.originalException)
.toContainError( .toContainError(
@ -165,8 +156,7 @@ export function main() {
inject( inject(
[AsyncTestCompleter], [AsyncTestCompleter],
(async) => { (async) => {
bootstrap(BadAliasCmp, bootstrap(BadAliasCmp, testBindings)
[bind(ROUTER_PRIMARY_COMPONENT).toValue(BadAliasCmp), testBindings])
.catch((e) => { .catch((e) => {
expect(e.originalException) expect(e.originalException)
.toContainError( .toContainError(

View File

@ -1,13 +1,12 @@
import {InboxApp} from './inbox-app'; import {InboxApp} from './inbox-app';
import {bind} from 'angular2/angular2'; import {bind} from 'angular2/angular2';
import {bootstrap} from 'angular2/bootstrap'; import {bootstrap} from 'angular2/bootstrap';
import {routerBindings, HashLocationStrategy, LocationStrategy} from 'angular2/router'; import {ROUTER_BINDINGS, HashLocationStrategy, LocationStrategy} from 'angular2/router';
import {reflector} from 'angular2/src/core/reflection/reflection'; import {reflector} from 'angular2/src/core/reflection/reflection';
import {ReflectionCapabilities} from 'angular2/src/core/reflection/reflection_capabilities'; import {ReflectionCapabilities} from 'angular2/src/core/reflection/reflection_capabilities';
export function main() { export function main() {
reflector.reflectionCapabilities = new ReflectionCapabilities(); reflector.reflectionCapabilities = new ReflectionCapabilities();
bootstrap(InboxApp, bootstrap(InboxApp, [ROUTER_BINDINGS, bind(LocationStrategy).toClass(HashLocationStrategy)]);
[routerBindings(InboxApp), bind(LocationStrategy).toClass(HashLocationStrategy)]);
} }