Skip to content

Commit 4965226

Browse files
committed
fix(router): use lists for RouteConfig annotations
1 parent ea546f5 commit 4965226

File tree

7 files changed

+115
-62
lines changed

7 files changed

+115
-62
lines changed
Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
import {CONST} from 'angular2/src/facade/lang';
2+
import {List} from 'angular2/src/facade/collection';
23

34
/**
45
* You use the RouteConfig annotation to ...
56
*/
67
export class RouteConfig {
7-
path:string;
8-
redirectTo:string;
9-
component:any;
10-
//TODO: "alias," or "as"
11-
8+
configs;
129
@CONST()
13-
constructor({path, component, redirectTo}:{path:string, component:any, redirectTo:string} = {}) {
14-
this.path = path;
15-
this.component = component;
16-
this.redirectTo = redirectTo;
10+
constructor(configs:List) {
11+
this.configs = configs;
1712
}
1813
}

modules/angular2/src/router/route_recognizer.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,16 @@ export class RouteRecognizer {
4141
var solution = StringMapWrapper.create();
4242
StringMapWrapper.set(solution, 'handler', pathRecognizer.handler);
4343
StringMapWrapper.set(solution, 'params', pathRecognizer.parseParams(url));
44-
StringMapWrapper.set(solution, 'matchedUrl', match[0]);
45-
46-
var unmatchedUrl = StringWrapper.substring(url, match[0].length);
47-
StringMapWrapper.set(solution, 'unmatchedUrl', unmatchedUrl);
4844

45+
//TODO(btford): determine a good generic way to deal with terminal matches
46+
if (url === '/') {
47+
StringMapWrapper.set(solution, 'matchedUrl', '/');
48+
StringMapWrapper.set(solution, 'unmatchedUrl', '');
49+
} else {
50+
StringMapWrapper.set(solution, 'matchedUrl', match[0]);
51+
var unmatchedUrl = StringWrapper.substring(url, match[0].length);
52+
StringMapWrapper.set(solution, 'unmatchedUrl', unmatchedUrl);
53+
}
4954
ListWrapper.push(solutions, solution);
5055
}
5156
});

modules/angular2/src/router/route_registry.js

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
import {RouteRecognizer} from './route_recognizer';
22
import {Instruction, noopInstruction} from './instruction';
33
import {List, ListWrapper, Map, MapWrapper, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
4-
import {isPresent, isBlank, isType, StringWrapper} from 'angular2/src/facade/lang';
4+
import {isPresent, isBlank, isType, StringWrapper, CONST} from 'angular2/src/facade/lang';
55
import {RouteConfig} from './route_config';
66
import {reflector} from 'angular2/src/reflection/reflection';
77

8+
export const rootHostComponent = 'ROOT_HOST';
9+
810
export class RouteRegistry {
911
_rules:Map<any, RouteRecognizer>;
1012

1113
constructor() {
1214
this._rules = MapWrapper.create();
1315
}
1416

15-
config(parentComponent, path:string, component:any, alias:string = null) {
16-
if (parentComponent === 'app') {
17-
parentComponent = '/';
18-
}
17+
config(parentComponent, config) {
1918

2019
var recognizer:RouteRecognizer;
2120
if (MapWrapper.contains(this._rules, parentComponent)) {
@@ -25,16 +24,14 @@ export class RouteRegistry {
2524
MapWrapper.set(this._rules, parentComponent, recognizer);
2625
}
2726

28-
this._configFromComponent(component);
29-
30-
//TODO: support sibling components
31-
var components = StringMapWrapper.create();
32-
StringMapWrapper.set(components, 'default', component);
27+
config = normalizeConfig(config);
3328

34-
var handler = StringMapWrapper.create();
35-
StringMapWrapper.set(handler, 'components', components);
29+
var components = StringMapWrapper.get(config, 'components');
30+
StringMapWrapper.forEach(components, (component, _) => {
31+
this._configFromComponent(component);
32+
});
3633

37-
recognizer.addConfig(path, handler, alias);
34+
recognizer.addConfig(config['path'], config, config['alias']);
3835
}
3936

4037
_configFromComponent(component) {
@@ -53,7 +50,9 @@ export class RouteRegistry {
5350
var annotation = annotations[i];
5451

5552
if (annotation instanceof RouteConfig) {
56-
this.config(component, annotation.path, annotation.component);
53+
ListWrapper.forEach(annotation.configs, (config) => {
54+
this.config(component, config);
55+
})
5756
}
5857
}
5958
}
@@ -62,7 +61,7 @@ export class RouteRegistry {
6261

6362
// TODO: make recognized context a class
6463
// TODO: change parentComponent into parentContext
65-
recognize(url:string, parentComponent = '/') {
64+
recognize(url:string, parentComponent = rootHostComponent) {
6665
var componentRecognizer = MapWrapper.get(this._rules, parentComponent);
6766
if (isBlank(componentRecognizer)) {
6867
return null;
@@ -106,7 +105,7 @@ export class RouteRegistry {
106105

107106
generate(name:string, params:any) {
108107
//TODO: implement for hierarchical routes
109-
var componentRecognizer = MapWrapper.get(this._rules, '/');
108+
var componentRecognizer = MapWrapper.get(this._rules, rootHostComponent);
110109
if (isPresent(componentRecognizer)) {
111110
return componentRecognizer.generate(name, params);
112111
}
@@ -127,3 +126,29 @@ function handlerToLeafInstructions(context, parentComponent) {
127126
matchedUrl: context['matchedUrl']
128127
});
129128
}
129+
130+
// given:
131+
// { component: Foo }
132+
// mutates the config to:
133+
// { components: { default: Foo } }
134+
function normalizeConfig(config:StringMap) {
135+
if (StringMapWrapper.contains(config, 'component')) {
136+
var component = StringMapWrapper.get(config, 'component');
137+
var components = StringMapWrapper.create();
138+
StringMapWrapper.set(components, 'default', component);
139+
140+
var newConfig = StringMapWrapper.create();
141+
StringMapWrapper.set(newConfig, 'components', components);
142+
143+
StringMapWrapper.forEach(config, (value, key) => {
144+
if (!StringWrapper.equals(key, 'component') && !StringWrapper.equals(key, 'components')) {
145+
StringMapWrapper.set(newConfig, key, value);
146+
}
147+
});
148+
149+
return newConfig;
150+
} else if (!StringMapWrapper.contains(config, 'components')) {
151+
throw new Error('Config does not include a "component" or "components" key.');
152+
}
153+
return config;
154+
}

modules/angular2/src/router/router.js

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {Promise, PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2
22
import {Map, MapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
33
import {isBlank} from 'angular2/src/facade/lang';
44

5-
import {RouteRegistry} from './route_registry';
5+
import {RouteRegistry, rootHostComponent} from './route_registry';
66
import {Pipeline} from './pipeline';
77
import {Instruction} from './instruction';
88
import {RouterOutlet} from './router_outlet';
@@ -18,7 +18,7 @@ import {Location} from './location';
1818
* @exportedAs angular2/router
1919
*/
2020
export class Router {
21-
name;
21+
hostComponent:any;
2222
parent:Router;
2323
navigating:boolean;
2424
lastNavigationAttempt: string;
@@ -30,9 +30,9 @@ export class Router {
3030
_children:Map<any, Router>;
3131
_subject:EventEmitter;
3232
_location:Location;
33-
34-
constructor(registry:RouteRegistry, pipeline:Pipeline, location:Location, parent:Router = null, name = '/') {
35-
this.name = name;
33+
34+
constructor(registry:RouteRegistry, pipeline:Pipeline, location:Location, parent:Router, hostComponent) {
35+
this.hostComponent = hostComponent;
3636
this.navigating = false;
3737
this.parent = parent;
3838
this.previousUrl = null;
@@ -42,8 +42,7 @@ export class Router {
4242
this._registry = registry;
4343
this._pipeline = pipeline;
4444
this._subject = new EventEmitter();
45-
this._location.subscribe((url) => this.navigate(url));
46-
this.navigate(location.path());
45+
//this._location.subscribe((url) => this.navigate(url));
4746
}
4847

4948

@@ -73,11 +72,30 @@ export class Router {
7372
* # Usage
7473
*
7574
* ```
76-
* router.config('/', SomeCmp);
75+
* router.config({ 'path': '/', 'component': IndexCmp});
76+
* ```
77+
*
78+
* Or:
79+
*
7780
* ```
81+
* router.config([
82+
* { 'path': '/', 'component': IndexComp },
83+
* { 'path': '/user/:id', 'component': UserComp },
84+
* ]);
85+
* ```
86+
*
7887
*/
79-
config(path:string, component, alias:string=null) {
80-
this._registry.config(this.name, path, component, alias);
88+
config(config:any) {
89+
90+
//TODO: use correct check
91+
if (config instanceof List) {
92+
path.forEach((configObject) => {
93+
// TODO: this is a hack
94+
this._registry.config(this.hostComponent, configObject);
95+
})
96+
} else {
97+
this._registry.config(this.hostComponent, config);
98+
}
8199
return this.renavigate();
82100
}
83101

@@ -184,13 +202,14 @@ export class Router {
184202

185203
export class RootRouter extends Router {
186204
constructor(pipeline:Pipeline, location:Location) {
187-
super(new RouteRegistry(), pipeline, location, null, '/');
205+
super(new RouteRegistry(), pipeline, location, null, rootHostComponent);
206+
this.navigate(location.path());
188207
}
189208
}
190209

191210
class ChildRouter extends Router {
192-
constructor(parent, name) {
193-
super(parent._registry, parent._pipeline, parent._location, parent, name);
211+
constructor(parent:Router, hostComponent) {
212+
super(parent._registry, parent._pipeline, parent._location, parent, hostComponent);
194213
this.parent = parent;
195214
}
196215
}

modules/angular2/test/router/outlet_spec.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function main() {
5353

5454
it('should work in a simple case', inject([AsyncTestCompleter], (async) => {
5555
compile()
56-
.then((_) => router.config('/test', HelloCmp))
56+
.then((_) => router.config({'path': '/test', 'component': HelloCmp}))
5757
.then((_) => router.navigate('/test'))
5858
.then((_) => {
5959
view.detectChanges();
@@ -65,7 +65,7 @@ export function main() {
6565

6666
it('should navigate between components with different parameters', inject([AsyncTestCompleter], (async) => {
6767
compile()
68-
.then((_) => router.config('/user/:name', UserCmp))
68+
.then((_) => router.config({'path': '/user/:name', 'component': UserCmp}))
6969
.then((_) => router.navigate('/user/brian'))
7070
.then((_) => {
7171
view.detectChanges();
@@ -82,7 +82,7 @@ export function main() {
8282

8383
it('should work with child routers', inject([AsyncTestCompleter], (async) => {
8484
compile('outer { <router-outlet></router-outlet> }')
85-
.then((_) => router.config('/a', ParentCmp))
85+
.then((_) => router.config({'path': '/a', 'component': ParentCmp}))
8686
.then((_) => router.navigate('/a/b'))
8787
.then((_) => {
8888
view.detectChanges();
@@ -95,7 +95,7 @@ export function main() {
9595
it('should generate link hrefs', inject([AsyncTestCompleter], (async) => {
9696
ctx.name = 'brian';
9797
compile('<a href="hello" router-link="user" [router-params]="{name: name}">{{name}}</a>')
98-
.then((_) => router.config('/user/:name', UserCmp, 'user'))
98+
.then((_) => router.config({'path': '/user/:name', 'component': UserCmp, 'alias': 'user'}))
9999
.then((_) => router.navigate('/a/b'))
100100
.then((_) => {
101101
view.detectChanges();
@@ -144,10 +144,10 @@ class UserCmp {
144144
template: "inner { <router-outlet></router-outlet> }",
145145
directives: [RouterOutlet]
146146
})
147-
@RouteConfig({
147+
@RouteConfig([{
148148
path: '/b',
149149
component: HelloCmp
150-
})
150+
}])
151151
class ParentCmp {
152152
constructor() {}
153153
}

modules/angular2/test/router/route_registry_spec.js

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,45 @@ import {
66
inject, beforeEach,
77
SpyObject} from 'angular2/test_lib';
88

9-
import {RouteRegistry} from 'angular2/src/router/route_registry';
9+
import {RouteRegistry, rootHostComponent} from 'angular2/src/router/route_registry';
10+
import {RouteConfig} from 'angular2/src/router/route_config';
1011

1112
export function main() {
1213
describe('RouteRegistry', () => {
1314
var registry;
14-
var handler = {};
15-
var handler2 = {};
1615

1716
beforeEach(() => {
1817
registry = new RouteRegistry();
1918
});
2019

2120
it('should match the full URL', () => {
22-
registry.config('/', '/', handler);
23-
registry.config('/', '/test', handler2);
21+
registry.config(rootHostComponent, {'path': '/', 'component': DummyCompA});
22+
registry.config(rootHostComponent, {'path': '/test', 'component': DummyCompB});
2423

2524
var instruction = registry.recognize('/test');
2625

27-
expect(instruction.getChildInstruction('default').component).toBe(handler2);
26+
expect(instruction.getChildInstruction('default').component).toBe(DummyCompB);
2827
});
2928

3029
it('should match the full URL recursively', () => {
31-
registry.config('/', '/first', handler);
32-
registry.config(handler, '/second', handler2);
30+
registry.config(rootHostComponent, {'path': '/first', 'component': DummyParentComp});
3331

3432
var instruction = registry.recognize('/first/second');
3533

36-
expect(instruction.getChildInstruction('default').component).toBe(handler);
37-
expect(instruction.getChildInstruction('default').getChildInstruction('default').component).toBe(handler2);
34+
var parentInstruction = instruction.getChildInstruction('default');
35+
var childInstruction = parentInstruction.getChildInstruction('default');
36+
37+
expect(parentInstruction.component).toBe(DummyParentComp);
38+
expect(childInstruction.component).toBe(DummyCompB);
3839
});
3940

4041
});
4142
}
43+
44+
@RouteConfig([
45+
{'path': '/second', 'component': DummyCompB }
46+
])
47+
class DummyParentComp {}
48+
49+
class DummyCompA {}
50+
class DummyCompB {}

modules/angular2/test/router/router_spec.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ export function main() {
2828
it('should navigate based on the initial URL state', inject([AsyncTestCompleter], (async) => {
2929
var outlet = makeDummyRef();
3030

31-
router.config('/', {'component': 'Index' })
31+
router.config({'path': '/', 'component': 'Index' })
3232
.then((_) => router.registerOutlet(outlet))
3333
.then((_) => {
3434
expect(outlet.spy('activate')).toHaveBeenCalled();
35-
expect(location.urlChanges).toEqual(['/']);
35+
expect(location.urlChanges).toEqual([]);
3636
async.done();
3737
});
3838
}));
@@ -43,7 +43,7 @@ export function main() {
4343

4444
router.registerOutlet(outlet)
4545
.then((_) => {
46-
return router.config('/a', {'component': 'A' });
46+
return router.config({'path': '/a', 'component': 'A' });
4747
})
4848
.then((_) => router.navigate('/a'))
4949
.then((_) => {
@@ -60,7 +60,7 @@ export function main() {
6060
.then((_) => router.navigate('/a'))
6161
.then((_) => {
6262
expect(outlet.spy('activate')).not.toHaveBeenCalled();
63-
return router.config('/a', {'component': 'A' });
63+
return router.config({'path': '/a', 'component': 'A' });
6464
})
6565
.then((_) => {
6666
expect(outlet.spy('activate')).toHaveBeenCalled();

0 commit comments

Comments
 (0)