Skip to content

Commit b87da8f

Browse files
cexbrayatbtford
authored andcommitted
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 angular#4392 Closes angular#4428
1 parent fbe748f commit b87da8f

11 files changed

Lines changed: 79 additions & 67 deletions

File tree

modules/angular2/router.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
export {Router} from './src/router/router';
88
export {RouterOutlet} from './src/router/router_outlet';
99
export {RouterLink} from './src/router/router_link';
10-
export {RouteParams} from './src/router/instruction';
10+
export {RouteParams, RouteData} from './src/router/instruction';
1111
export {RouteRegistry} from './src/router/route_registry';
1212
export {LocationStrategy} from './src/router/location_strategy';
1313
export {HashLocationStrategy} from './src/router/hash_location_strategy';
@@ -19,7 +19,6 @@ export {OnActivate, OnDeactivate, OnReuse, CanDeactivate, CanReuse} from './src/
1919
export {CanActivate} from './src/router/lifecycle_annotations';
2020
export {Instruction, ComponentInstruction} from './src/router/instruction';
2121
export {OpaqueToken} from 'angular2/angular2';
22-
export {ROUTE_DATA} from './src/router/route_data';
2322

2423
import {LocationStrategy} from './src/router/location_strategy';
2524
import {PathLocationStrategy} from './src/router/path_location_strategy';

modules/angular2/src/router/async_route_handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export class AsyncRouteHandler implements RouteHandler {
77
_resolvedComponent: Promise<any> = null;
88
componentType: Type;
99

10-
constructor(private _loader: Function, public data?: Object) {}
10+
constructor(private _loader: Function, public data?: {[key: string]: any}) {}
1111

1212
resolveComponentType(): Promise<any> {
1313
if (isPresent(this._resolvedComponent)) {

modules/angular2/src/router/instruction.ts

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Map, MapWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/core/facade/collection';
22
import {unimplemented} from 'angular2/src/core/facade/exceptions';
3-
import {isPresent, isBlank, normalizeBlank, Type} from 'angular2/src/core/facade/lang';
3+
import {isPresent, isBlank, normalizeBlank, Type, CONST_EXPR} from 'angular2/src/core/facade/lang';
44
import {Promise} from 'angular2/src/core/facade/async';
55

66
import {PathRecognizer} from './path_recognizer';
@@ -41,6 +41,44 @@ export class RouteParams {
4141
get(param: string): string { return normalizeBlank(StringMapWrapper.get(this.params, param)); }
4242
}
4343

44+
/**
45+
* `RouteData` is an immutable map of additional data you can configure in your {@link Route}.
46+
*
47+
* You can inject `RouteData` into the constructor of a component to use it.
48+
*
49+
* ## Example
50+
*
51+
* ```
52+
* import {bootstrap, Component, View} from 'angular2/angular2';
53+
* import {Router, ROUTER_DIRECTIVES, routerBindings, RouteConfig} from 'angular2/router';
54+
*
55+
* @Component({...})
56+
* @View({directives: [ROUTER_DIRECTIVES]})
57+
* @RouteConfig([
58+
* {path: '/user/:id', component: UserCmp, as: 'UserCmp', data: {isAdmin: true}},
59+
* ])
60+
* class AppCmp {}
61+
*
62+
* @Component({...})
63+
* @View({ template: 'user: {{isAdmin}}' })
64+
* class UserCmp {
65+
* string: isAdmin;
66+
* constructor(data: RouteData) {
67+
* this.isAdmin = data.get('isAdmin');
68+
* }
69+
* }
70+
*
71+
* bootstrap(AppCmp, routerBindings(AppCmp));
72+
* ```
73+
*/
74+
export class RouteData {
75+
constructor(public data: {[key: string]: any} = CONST_EXPR({})) {}
76+
77+
get(key: string): any { return normalizeBlank(StringMapWrapper.get(this.data, key)); }
78+
}
79+
80+
var BLANK_ROUTE_DATA = new RouteData();
81+
4482
/**
4583
* `Instruction` is a tree of {@link ComponentInstruction}s with all the information needed
4684
* to transition each component in the app to a given route, including all auxiliary routes.
@@ -176,23 +214,30 @@ export abstract class ComponentInstruction {
176214

177215
/**
178216
* Returns the route data of the given route that was specified in the {@link RouteDefinition},
179-
* or `null` if no route data was specified.
217+
* or an empty object if no route data was specified.
180218
*/
181-
abstract routeData(): Object;
219+
get routeData(): RouteData { return unimplemented(); };
182220
}
183221

184222
export class ComponentInstruction_ extends ComponentInstruction {
223+
private _routeData: RouteData;
224+
185225
constructor(urlPath: string, urlParams: string[], private _recognizer: PathRecognizer,
186226
params: {[key: string]: any} = null) {
187227
super();
188228
this.urlPath = urlPath;
189229
this.urlParams = urlParams;
190230
this.params = params;
231+
if (isPresent(this._recognizer.handler.data)) {
232+
this._routeData = new RouteData(this._recognizer.handler.data);
233+
} else {
234+
this._routeData = BLANK_ROUTE_DATA;
235+
}
191236
}
192237

193238
get componentType() { return this._recognizer.handler.componentType; }
194239
resolveComponentType(): Promise<Type> { return this._recognizer.handler.resolveComponentType(); }
195240
get specificity() { return this._recognizer.specificity; }
196241
get terminal() { return this._recognizer.terminal; }
197-
routeData(): Object { return this._recognizer.handler.data; }
242+
get routeData(): RouteData { return this._routeData; }
198243
}

modules/angular2/src/router/route_config_impl.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class RouteConfig {
2020
* - `component` a component type.
2121
* - `as` is an optional `CamelCase` string representing the name of the route.
2222
* - `data` is an optional property of any type representing arbitrary route metadata for the given
23-
* route. It is injectable via the {@link ROUTE_DATA} token.
23+
* route. It is injectable via {@link RouteData}.
2424
*
2525
* ### Example
2626
* ```
@@ -34,15 +34,15 @@ export class RouteConfig {
3434
*/
3535
@CONST()
3636
export class Route implements RouteDefinition {
37-
data: any;
37+
data: {[key: string]: any};
3838
path: string;
3939
component: Type;
4040
as: string;
4141
// added next two properties to work around https://github.com/Microsoft/TypeScript/issues/4107
4242
loader: Function;
4343
redirectTo: string;
4444
constructor({path, component, as, data}:
45-
{path: string, component: Type, as?: string, data?: any}) {
45+
{path: string, component: Type, as?: string, data?: {[key: string]: any}}) {
4646
this.path = path;
4747
this.component = component;
4848
this.as = as;
@@ -60,7 +60,7 @@ export class Route implements RouteDefinition {
6060
* - `component` a component type.
6161
* - `as` is an optional `CamelCase` string representing the name of the route.
6262
* - `data` is an optional property of any type representing arbitrary route metadata for the given
63-
* route. It is injectable via the {@link ROUTE_DATA} token.
63+
* route. It is injectable via {@link RouteData}.
6464
*
6565
* ### Example
6666
* ```
@@ -74,7 +74,7 @@ export class Route implements RouteDefinition {
7474
*/
7575
@CONST()
7676
export class AuxRoute implements RouteDefinition {
77-
data: any = null;
77+
data: {[key: string]: any} = null;
7878
path: string;
7979
component: Type;
8080
as: string;
@@ -97,7 +97,7 @@ export class AuxRoute implements RouteDefinition {
9797
* - `loader` is a function that returns a promise that resolves to a component.
9898
* - `as` is an optional `CamelCase` string representing the name of the route.
9999
* - `data` is an optional property of any type representing arbitrary route metadata for the given
100-
* route. It is injectable via the {@link ROUTE_DATA} token.
100+
* route. It is injectable via {@link RouteData}.
101101
*
102102
* ### Example
103103
* ```
@@ -111,11 +111,12 @@ export class AuxRoute implements RouteDefinition {
111111
*/
112112
@CONST()
113113
export class AsyncRoute implements RouteDefinition {
114-
data: any;
114+
data: {[key: string]: any};
115115
path: string;
116116
loader: Function;
117117
as: string;
118-
constructor({path, loader, as, data}: {path: string, loader: Function, as?: string, data?: any}) {
118+
constructor({path, loader, as, data}:
119+
{path: string, loader: Function, as?: string, data?: {[key: string]: any}}) {
119120
this.path = path;
120121
this.loader = loader;
121122
this.as = as;

modules/angular2/src/router/route_data.ts

Lines changed: 0 additions & 4 deletions
This file was deleted.

modules/angular2/src/router/route_handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ import {Type} from 'angular2/src/core/facade/lang';
44
export interface RouteHandler {
55
componentType: Type;
66
resolveComponentType(): Promise<any>;
7-
data?: Object;
7+
data?: {[key: string]: any};
88
}

modules/angular2/src/router/router_outlet.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ import {
1515
} from 'angular2/angular2';
1616

1717
import * as routerMod from './router';
18-
import {ComponentInstruction, RouteParams} from './instruction';
19-
import {ROUTE_DATA} from './route_data';
18+
import {ComponentInstruction, RouteParams, RouteData} from './instruction';
2019
import * as hookMod from './lifecycle_annotations';
2120
import {hasLifecycleHook} from './route_lifecycle_reflector';
2221

@@ -58,7 +57,7 @@ export class RouterOutlet {
5857
var childRouter = this._parentRouter.childRouter(componentType);
5958

6059
var providers = Injector.resolve([
61-
provide(ROUTE_DATA, {useValue: nextInstruction.routeData()}),
60+
provide(RouteData, {useValue: nextInstruction.routeData}),
6261
provide(RouteParams, {useValue: new RouteParams(nextInstruction.params)}),
6362
provide(routerMod.Router, {useValue: childRouter})
6463
]);

modules/angular2/src/router/sync_route_handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export class SyncRouteHandler implements RouteHandler {
66
/** @internal */
77
_resolvedComponent: Promise<any> = null;
88

9-
constructor(public componentType: Type, public data?: Object) {
9+
constructor(public componentType: Type, public data?: {[key: string]: any}) {
1010
this._resolvedComponent = PromiseWrapper.resolve(componentType);
1111
}
1212

modules/angular2/test/router/integration/lifecycle_hook_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
} from 'angular2/src/core/facade/async';
2727

2828
import {RootRouter} from 'angular2/src/router/router';
29-
import {Router, RouterOutlet, RouterLink, RouteParams, ROUTE_DATA} from 'angular2/router';
29+
import {Router, RouterOutlet, RouterLink, RouteParams} from 'angular2/router';
3030
import {
3131
RouteConfig,
3232
Route,

modules/angular2/test/router/integration/navigation_spec.ts

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@ import {
1616
} from 'angular2/testing_internal';
1717

1818
import {provide, Component, View, Injector, Inject} from 'angular2/core';
19-
import {CONST, NumberWrapper, isPresent, Json} from 'angular2/src/core/facade/lang';
2019
import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async';
2120

2221
import {RootRouter} from 'angular2/src/router/router';
23-
import {Router, RouterOutlet, RouterLink, RouteParams, ROUTE_DATA} from 'angular2/router';
22+
import {Router, RouterOutlet, RouterLink, RouteParams, RouteData} from 'angular2/router';
2423
import {
2524
RouteConfig,
2625
Route,
@@ -200,13 +199,12 @@ export function main() {
200199
it('should inject route data into component', inject([AsyncTestCompleter], (async) => {
201200
compile()
202201
.then((_) => rtr.config([
203-
new Route({path: '/route-data', component: RouteDataCmp, data: {'isAdmin': true}})
202+
new Route({path: '/route-data', component: RouteDataCmp, data: {isAdmin: true}})
204203
]))
205204
.then((_) => rtr.navigateByUrl('/route-data'))
206205
.then((_) => {
207206
rootTC.detectChanges();
208-
expect(rootTC.debugElement.nativeElement)
209-
.toHaveText(Json.stringify({'isAdmin': true}));
207+
expect(rootTC.debugElement.nativeElement).toHaveText('true');
210208
async.done();
211209
});
212210
}));
@@ -221,48 +219,20 @@ export function main() {
221219
.then((_) => rtr.navigateByUrl('/route-data'))
222220
.then((_) => {
223221
rootTC.detectChanges();
224-
expect(rootTC.debugElement.nativeElement)
225-
.toHaveText(Json.stringify({'isAdmin': true}));
222+
expect(rootTC.debugElement.nativeElement).toHaveText('true');
226223
async.done();
227224
});
228225
}));
229226

230-
it('should inject null if the route has no data property',
227+
it('should inject empty object if the route has no data property',
231228
inject([AsyncTestCompleter], (async) => {
232229
compile()
233230
.then((_) => rtr.config(
234231
[new Route({path: '/route-data-default', component: RouteDataCmp})]))
235232
.then((_) => rtr.navigateByUrl('/route-data-default'))
236233
.then((_) => {
237234
rootTC.detectChanges();
238-
expect(rootTC.debugElement.nativeElement).toHaveText('null');
239-
async.done();
240-
});
241-
}));
242-
243-
it('should allow an array as the route data', inject([AsyncTestCompleter], (async) => {
244-
compile()
245-
.then((_) => rtr.config([
246-
new Route({path: '/route-data-array', component: RouteDataCmp, data: [1, 2, 3]})
247-
]))
248-
.then((_) => rtr.navigateByUrl('/route-data-array'))
249-
.then((_) => {
250-
rootTC.detectChanges();
251-
expect(rootTC.debugElement.nativeElement).toHaveText(Json.stringify([1, 2, 3]));
252-
async.done();
253-
});
254-
}));
255-
256-
it('should allow a string as the route data', inject([AsyncTestCompleter], (async) => {
257-
compile()
258-
.then((_) => rtr.config([
259-
new Route(
260-
{path: '/route-data-string', component: RouteDataCmp, data: 'hello world'})
261-
]))
262-
.then((_) => rtr.navigateByUrl('/route-data-string'))
263-
.then((_) => {
264-
rootTC.detectChanges();
265-
expect(rootTC.debugElement.nativeElement).toHaveText(Json.stringify('hello world'));
235+
expect(rootTC.debugElement.nativeElement).toHaveText('');
266236
async.done();
267237
});
268238
}));
@@ -298,10 +268,8 @@ function AsyncRouteDataCmp() {
298268
@Component({selector: 'data-cmp'})
299269
@View({template: "{{myData}}"})
300270
class RouteDataCmp {
301-
myData: string;
302-
constructor(@Inject(ROUTE_DATA) data: any) {
303-
this.myData = isPresent(data) ? Json.stringify(data) : 'null';
304-
}
271+
myData: boolean;
272+
constructor(data: RouteData) { this.myData = data.get('isAdmin'); }
305273
}
306274

307275
@Component({selector: 'user-cmp'})

0 commit comments

Comments
 (0)