From b87da8f47c340e0539213709c4a3407db6e12a45 Mon Sep 17 00:00:00 2001 From: cexbrayat Date: Wed, 30 Sep 2015 14:48:58 +0200 Subject: [PATCH] refactor(router): RouteData as a type BREAKING CHANGE The ROUTE_DATA token has been removed and replaced with a type RouteData, allowing a type injection like we do with RouteParams. Before: constructor(routeParams: RouteParams, @Inject(ROUTE_DATA) routeData) { let id = routeParams.get('id'); let name = ROUTE_DATA.name; } After: constructor(routeParams: RouteParams, routeData: RouteData) { let id = routeParams.get('id'); let name = routeData.get('name'); } Fixes #4392 Closes #4428 --- modules/angular2/router.ts | 3 +- .../src/router/async_route_handler.ts | 2 +- modules/angular2/src/router/instruction.ts | 53 +++++++++++++++++-- .../angular2/src/router/route_config_impl.ts | 17 +++--- modules/angular2/src/router/route_data.ts | 4 -- modules/angular2/src/router/route_handler.ts | 2 +- modules/angular2/src/router/router_outlet.ts | 5 +- .../angular2/src/router/sync_route_handler.ts | 2 +- .../router/integration/lifecycle_hook_spec.ts | 2 +- .../router/integration/navigation_spec.ts | 48 +++-------------- .../angular2/test/router/router_link_spec.ts | 8 ++- 11 files changed, 79 insertions(+), 67 deletions(-) delete mode 100644 modules/angular2/src/router/route_data.ts diff --git a/modules/angular2/router.ts b/modules/angular2/router.ts index 0209117056..9fc456fbbd 100644 --- a/modules/angular2/router.ts +++ b/modules/angular2/router.ts @@ -7,7 +7,7 @@ export {Router} from './src/router/router'; export {RouterOutlet} from './src/router/router_outlet'; export {RouterLink} from './src/router/router_link'; -export {RouteParams} from './src/router/instruction'; +export {RouteParams, RouteData} from './src/router/instruction'; export {RouteRegistry} from './src/router/route_registry'; export {LocationStrategy} from './src/router/location_strategy'; export {HashLocationStrategy} from './src/router/hash_location_strategy'; @@ -19,7 +19,6 @@ export {OnActivate, OnDeactivate, OnReuse, CanDeactivate, CanReuse} from './src/ export {CanActivate} from './src/router/lifecycle_annotations'; export {Instruction, ComponentInstruction} from './src/router/instruction'; export {OpaqueToken} from 'angular2/angular2'; -export {ROUTE_DATA} from './src/router/route_data'; import {LocationStrategy} from './src/router/location_strategy'; import {PathLocationStrategy} from './src/router/path_location_strategy'; diff --git a/modules/angular2/src/router/async_route_handler.ts b/modules/angular2/src/router/async_route_handler.ts index 3e828b63de..bbc041b031 100644 --- a/modules/angular2/src/router/async_route_handler.ts +++ b/modules/angular2/src/router/async_route_handler.ts @@ -7,7 +7,7 @@ export class AsyncRouteHandler implements RouteHandler { _resolvedComponent: Promise = null; componentType: Type; - constructor(private _loader: Function, public data?: Object) {} + constructor(private _loader: Function, public data?: {[key: string]: any}) {} resolveComponentType(): Promise { if (isPresent(this._resolvedComponent)) { diff --git a/modules/angular2/src/router/instruction.ts b/modules/angular2/src/router/instruction.ts index c7907a6e39..198c54c27d 100644 --- a/modules/angular2/src/router/instruction.ts +++ b/modules/angular2/src/router/instruction.ts @@ -1,6 +1,6 @@ import {Map, MapWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/core/facade/collection'; import {unimplemented} from 'angular2/src/core/facade/exceptions'; -import {isPresent, isBlank, normalizeBlank, Type} from 'angular2/src/core/facade/lang'; +import {isPresent, isBlank, normalizeBlank, Type, CONST_EXPR} from 'angular2/src/core/facade/lang'; import {Promise} from 'angular2/src/core/facade/async'; import {PathRecognizer} from './path_recognizer'; @@ -41,6 +41,44 @@ export class RouteParams { get(param: string): string { return normalizeBlank(StringMapWrapper.get(this.params, param)); } } +/** + * `RouteData` is an immutable map of additional data you can configure in your {@link Route}. + * + * You can inject `RouteData` into the constructor of a component to use it. + * + * ## Example + * + * ``` + * import {bootstrap, Component, View} from 'angular2/angular2'; + * import {Router, ROUTER_DIRECTIVES, routerBindings, RouteConfig} from 'angular2/router'; + * + * @Component({...}) + * @View({directives: [ROUTER_DIRECTIVES]}) + * @RouteConfig([ + * {path: '/user/:id', component: UserCmp, as: 'UserCmp', data: {isAdmin: true}}, + * ]) + * class AppCmp {} + * + * @Component({...}) + * @View({ template: 'user: {{isAdmin}}' }) + * class UserCmp { + * string: isAdmin; + * constructor(data: RouteData) { + * this.isAdmin = data.get('isAdmin'); + * } + * } + * + * bootstrap(AppCmp, routerBindings(AppCmp)); + * ``` + */ +export class RouteData { + constructor(public data: {[key: string]: any} = CONST_EXPR({})) {} + + get(key: string): any { return normalizeBlank(StringMapWrapper.get(this.data, key)); } +} + +var BLANK_ROUTE_DATA = new RouteData(); + /** * `Instruction` is a tree of {@link ComponentInstruction}s with all the information needed * to transition each component in the app to a given route, including all auxiliary routes. @@ -176,23 +214,30 @@ export abstract class ComponentInstruction { /** * Returns the route data of the given route that was specified in the {@link RouteDefinition}, - * or `null` if no route data was specified. + * or an empty object if no route data was specified. */ - abstract routeData(): Object; + get routeData(): RouteData { return unimplemented(); }; } export class ComponentInstruction_ extends ComponentInstruction { + private _routeData: RouteData; + constructor(urlPath: string, urlParams: string[], private _recognizer: PathRecognizer, params: {[key: string]: any} = null) { super(); this.urlPath = urlPath; this.urlParams = urlParams; this.params = params; + if (isPresent(this._recognizer.handler.data)) { + this._routeData = new RouteData(this._recognizer.handler.data); + } else { + this._routeData = BLANK_ROUTE_DATA; + } } get componentType() { return this._recognizer.handler.componentType; } resolveComponentType(): Promise { return this._recognizer.handler.resolveComponentType(); } get specificity() { return this._recognizer.specificity; } get terminal() { return this._recognizer.terminal; } - routeData(): Object { return this._recognizer.handler.data; } + get routeData(): RouteData { return this._routeData; } } diff --git a/modules/angular2/src/router/route_config_impl.ts b/modules/angular2/src/router/route_config_impl.ts index 931a15c287..7fc72cff62 100644 --- a/modules/angular2/src/router/route_config_impl.ts +++ b/modules/angular2/src/router/route_config_impl.ts @@ -20,7 +20,7 @@ export class RouteConfig { * - `component` a component type. * - `as` is an optional `CamelCase` string representing the name of the route. * - `data` is an optional property of any type representing arbitrary route metadata for the given - * route. It is injectable via the {@link ROUTE_DATA} token. + * route. It is injectable via {@link RouteData}. * * ### Example * ``` @@ -34,7 +34,7 @@ export class RouteConfig { */ @CONST() export class Route implements RouteDefinition { - data: any; + data: {[key: string]: any}; path: string; component: Type; as: string; @@ -42,7 +42,7 @@ export class Route implements RouteDefinition { loader: Function; redirectTo: string; constructor({path, component, as, data}: - {path: string, component: Type, as?: string, data?: any}) { + {path: string, component: Type, as?: string, data?: {[key: string]: any}}) { this.path = path; this.component = component; this.as = as; @@ -60,7 +60,7 @@ export class Route implements RouteDefinition { * - `component` a component type. * - `as` is an optional `CamelCase` string representing the name of the route. * - `data` is an optional property of any type representing arbitrary route metadata for the given - * route. It is injectable via the {@link ROUTE_DATA} token. + * route. It is injectable via {@link RouteData}. * * ### Example * ``` @@ -74,7 +74,7 @@ export class Route implements RouteDefinition { */ @CONST() export class AuxRoute implements RouteDefinition { - data: any = null; + data: {[key: string]: any} = null; path: string; component: Type; as: string; @@ -97,7 +97,7 @@ export class AuxRoute implements RouteDefinition { * - `loader` is a function that returns a promise that resolves to a component. * - `as` is an optional `CamelCase` string representing the name of the route. * - `data` is an optional property of any type representing arbitrary route metadata for the given - * route. It is injectable via the {@link ROUTE_DATA} token. + * route. It is injectable via {@link RouteData}. * * ### Example * ``` @@ -111,11 +111,12 @@ export class AuxRoute implements RouteDefinition { */ @CONST() export class AsyncRoute implements RouteDefinition { - data: any; + data: {[key: string]: any}; path: string; loader: Function; as: string; - constructor({path, loader, as, data}: {path: string, loader: Function, as?: string, data?: any}) { + constructor({path, loader, as, data}: + {path: string, loader: Function, as?: string, data?: {[key: string]: any}}) { this.path = path; this.loader = loader; this.as = as; diff --git a/modules/angular2/src/router/route_data.ts b/modules/angular2/src/router/route_data.ts deleted file mode 100644 index b6469dc46c..0000000000 --- a/modules/angular2/src/router/route_data.ts +++ /dev/null @@ -1,4 +0,0 @@ -import {OpaqueToken} from 'angular2/angular2'; -import {CONST_EXPR} from 'angular2/src/core/facade/lang'; - -export const ROUTE_DATA: OpaqueToken = CONST_EXPR(new OpaqueToken('routeData')); diff --git a/modules/angular2/src/router/route_handler.ts b/modules/angular2/src/router/route_handler.ts index 9ce1d974b8..5ccc34bb19 100644 --- a/modules/angular2/src/router/route_handler.ts +++ b/modules/angular2/src/router/route_handler.ts @@ -4,5 +4,5 @@ import {Type} from 'angular2/src/core/facade/lang'; export interface RouteHandler { componentType: Type; resolveComponentType(): Promise; - data?: Object; + data?: {[key: string]: any}; } diff --git a/modules/angular2/src/router/router_outlet.ts b/modules/angular2/src/router/router_outlet.ts index a9fa9c61c1..787ed2c675 100644 --- a/modules/angular2/src/router/router_outlet.ts +++ b/modules/angular2/src/router/router_outlet.ts @@ -15,8 +15,7 @@ import { } from 'angular2/angular2'; import * as routerMod from './router'; -import {ComponentInstruction, RouteParams} from './instruction'; -import {ROUTE_DATA} from './route_data'; +import {ComponentInstruction, RouteParams, RouteData} from './instruction'; import * as hookMod from './lifecycle_annotations'; import {hasLifecycleHook} from './route_lifecycle_reflector'; @@ -58,7 +57,7 @@ export class RouterOutlet { var childRouter = this._parentRouter.childRouter(componentType); var providers = Injector.resolve([ - provide(ROUTE_DATA, {useValue: nextInstruction.routeData()}), + provide(RouteData, {useValue: nextInstruction.routeData}), provide(RouteParams, {useValue: new RouteParams(nextInstruction.params)}), provide(routerMod.Router, {useValue: childRouter}) ]); diff --git a/modules/angular2/src/router/sync_route_handler.ts b/modules/angular2/src/router/sync_route_handler.ts index 61cc6a6bb0..75cdff1bba 100644 --- a/modules/angular2/src/router/sync_route_handler.ts +++ b/modules/angular2/src/router/sync_route_handler.ts @@ -6,7 +6,7 @@ export class SyncRouteHandler implements RouteHandler { /** @internal */ _resolvedComponent: Promise = null; - constructor(public componentType: Type, public data?: Object) { + constructor(public componentType: Type, public data?: {[key: string]: any}) { this._resolvedComponent = PromiseWrapper.resolve(componentType); } diff --git a/modules/angular2/test/router/integration/lifecycle_hook_spec.ts b/modules/angular2/test/router/integration/lifecycle_hook_spec.ts index 0c02bbe275..bb73b2519d 100644 --- a/modules/angular2/test/router/integration/lifecycle_hook_spec.ts +++ b/modules/angular2/test/router/integration/lifecycle_hook_spec.ts @@ -26,7 +26,7 @@ import { } from 'angular2/src/core/facade/async'; import {RootRouter} from 'angular2/src/router/router'; -import {Router, RouterOutlet, RouterLink, RouteParams, ROUTE_DATA} from 'angular2/router'; +import {Router, RouterOutlet, RouterLink, RouteParams} from 'angular2/router'; import { RouteConfig, Route, diff --git a/modules/angular2/test/router/integration/navigation_spec.ts b/modules/angular2/test/router/integration/navigation_spec.ts index 4184edb7d5..439ea4fe09 100644 --- a/modules/angular2/test/router/integration/navigation_spec.ts +++ b/modules/angular2/test/router/integration/navigation_spec.ts @@ -16,11 +16,10 @@ import { } from 'angular2/testing_internal'; import {provide, Component, View, Injector, Inject} from 'angular2/core'; -import {CONST, NumberWrapper, isPresent, Json} from 'angular2/src/core/facade/lang'; import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async'; import {RootRouter} from 'angular2/src/router/router'; -import {Router, RouterOutlet, RouterLink, RouteParams, ROUTE_DATA} from 'angular2/router'; +import {Router, RouterOutlet, RouterLink, RouteParams, RouteData} from 'angular2/router'; import { RouteConfig, Route, @@ -200,13 +199,12 @@ export function main() { it('should inject route data into component', inject([AsyncTestCompleter], (async) => { compile() .then((_) => rtr.config([ - new Route({path: '/route-data', component: RouteDataCmp, data: {'isAdmin': true}}) + new Route({path: '/route-data', component: RouteDataCmp, data: {isAdmin: true}}) ])) .then((_) => rtr.navigateByUrl('/route-data')) .then((_) => { rootTC.detectChanges(); - expect(rootTC.debugElement.nativeElement) - .toHaveText(Json.stringify({'isAdmin': true})); + expect(rootTC.debugElement.nativeElement).toHaveText('true'); async.done(); }); })); @@ -221,13 +219,12 @@ export function main() { .then((_) => rtr.navigateByUrl('/route-data')) .then((_) => { rootTC.detectChanges(); - expect(rootTC.debugElement.nativeElement) - .toHaveText(Json.stringify({'isAdmin': true})); + expect(rootTC.debugElement.nativeElement).toHaveText('true'); async.done(); }); })); - it('should inject null if the route has no data property', + it('should inject empty object if the route has no data property', inject([AsyncTestCompleter], (async) => { compile() .then((_) => rtr.config( @@ -235,34 +232,7 @@ export function main() { .then((_) => rtr.navigateByUrl('/route-data-default')) .then((_) => { rootTC.detectChanges(); - expect(rootTC.debugElement.nativeElement).toHaveText('null'); - async.done(); - }); - })); - - it('should allow an array as the route data', inject([AsyncTestCompleter], (async) => { - compile() - .then((_) => rtr.config([ - new Route({path: '/route-data-array', component: RouteDataCmp, data: [1, 2, 3]}) - ])) - .then((_) => rtr.navigateByUrl('/route-data-array')) - .then((_) => { - rootTC.detectChanges(); - expect(rootTC.debugElement.nativeElement).toHaveText(Json.stringify([1, 2, 3])); - async.done(); - }); - })); - - it('should allow a string as the route data', inject([AsyncTestCompleter], (async) => { - compile() - .then((_) => rtr.config([ - new Route( - {path: '/route-data-string', component: RouteDataCmp, data: 'hello world'}) - ])) - .then((_) => rtr.navigateByUrl('/route-data-string')) - .then((_) => { - rootTC.detectChanges(); - expect(rootTC.debugElement.nativeElement).toHaveText(Json.stringify('hello world')); + expect(rootTC.debugElement.nativeElement).toHaveText(''); async.done(); }); })); @@ -298,10 +268,8 @@ function AsyncRouteDataCmp() { @Component({selector: 'data-cmp'}) @View({template: "{{myData}}"}) class RouteDataCmp { - myData: string; - constructor(@Inject(ROUTE_DATA) data: any) { - this.myData = isPresent(data) ? Json.stringify(data) : 'null'; - } + myData: boolean; + constructor(data: RouteData) { this.myData = data.get('isAdmin'); } } @Component({selector: 'user-cmp'}) diff --git a/modules/angular2/test/router/router_link_spec.ts b/modules/angular2/test/router/router_link_spec.ts index 074a6dd167..93df150286 100644 --- a/modules/angular2/test/router/router_link_spec.ts +++ b/modules/angular2/test/router/router_link_spec.ts @@ -32,9 +32,13 @@ import { } from 'angular2/router'; import {DOM} from 'angular2/src/core/dom/dom_adapter'; -import {ComponentInstruction_} from "angular2/src/router/instruction"; +import {ComponentInstruction_} from 'angular2/src/router/instruction'; +import {PathRecognizer} from 'angular2/src/router/path_recognizer'; +import {SyncRouteHandler} from 'angular2/src/router/sync_route_handler'; -var dummyInstruction = new Instruction(new ComponentInstruction_('detail', [], null), null, {}); +let dummyPathRecognizer = new PathRecognizer('', new SyncRouteHandler(null)); +let dummyInstruction = + new Instruction(new ComponentInstruction_('detail', [], dummyPathRecognizer), null, {}); export function main() { describe('router-link directive', function() {