X Tutup
Skip to content

Commit ef6163e

Browse files
committed
feat(router): implement recognizer
1 parent f698567 commit ef6163e

File tree

2 files changed

+173
-0
lines changed

2 files changed

+173
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import {RouteSegment, UrlSegment, Tree} from './segments';
2+
import {RoutesMetadata, RouteMetadata} from './metadata/metadata';
3+
import {Type, isPresent, stringify} from 'angular2/src/facade/lang';
4+
import {PromiseWrapper} from 'angular2/src/facade/promise';
5+
import {BaseException} from 'angular2/src/facade/exceptions';
6+
import {ComponentResolver} from 'angular2/core';
7+
import {reflector} from 'angular2/src/core/reflection/reflection';
8+
9+
export function recognize(componentResolver: ComponentResolver, type: Type,
10+
url: Tree<UrlSegment>): Promise<Tree<RouteSegment>> {
11+
return _recognize(componentResolver, type, url, url.root)
12+
.then(nodes => new Tree<RouteSegment>(nodes));
13+
}
14+
15+
function _recognize(componentResolver: ComponentResolver, type: Type, url: Tree<UrlSegment>,
16+
current: UrlSegment): Promise<RouteSegment[]> {
17+
let metadata = _readMetadata(type); // should read from the factory instead
18+
19+
let matched;
20+
try {
21+
matched = _match(metadata, url, current);
22+
} catch (e) {
23+
return PromiseWrapper.reject(e, null);
24+
}
25+
26+
return componentResolver.resolveComponent(matched.route.component)
27+
.then(factory => {
28+
let segment = new RouteSegment(matched.consumedUrlSegments, matched.parameters, "",
29+
matched.route.component, factory);
30+
31+
if (isPresent(matched.leftOver)) {
32+
return _recognize(componentResolver, matched.route.component, url, matched.leftOver)
33+
.then(children => [segment].concat(children));
34+
} else {
35+
return [segment];
36+
}
37+
});
38+
}
39+
40+
function _match(metadata: RoutesMetadata, url: Tree<UrlSegment>,
41+
current: UrlSegment): _MatchingResult {
42+
for (let r of metadata.routes) {
43+
let matchingResult = _matchWithParts(r, url, current);
44+
if (isPresent(matchingResult)) {
45+
return matchingResult;
46+
}
47+
}
48+
throw new BaseException("Cannot match any routes");
49+
}
50+
51+
function _matchWithParts(route: RouteMetadata, url: Tree<UrlSegment>,
52+
current: UrlSegment): _MatchingResult {
53+
let parts = route.path.split("/");
54+
let parameters = {};
55+
let consumedUrlSegments = [];
56+
57+
let u = current;
58+
for (let i = 0; i < parts.length; ++i) {
59+
consumedUrlSegments.push(u);
60+
let p = parts[i];
61+
if (p.startsWith(":")) {
62+
let segment = u.segment;
63+
parameters[p.substring(1)] = segment;
64+
} else if (p != u.segment) {
65+
return null;
66+
}
67+
u = url.firstChild(u);
68+
}
69+
return new _MatchingResult(route, consumedUrlSegments, parameters, u);
70+
}
71+
72+
class _MatchingResult {
73+
constructor(public route: RouteMetadata, public consumedUrlSegments: UrlSegment[],
74+
public parameters: {[key: string]: string}, public leftOver: UrlSegment) {}
75+
}
76+
77+
function _readMetadata(componentType: Type) {
78+
let metadata = reflector.annotations(componentType).filter(f => f instanceof RoutesMetadata);
79+
if (metadata.length === 0) {
80+
throw new BaseException(
81+
`Component '${stringify(componentType)}' does not have route configuration`);
82+
}
83+
return metadata[0];
84+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {
2+
ComponentFixture,
3+
AsyncTestCompleter,
4+
TestComponentBuilder,
5+
beforeEach,
6+
ddescribe,
7+
xdescribe,
8+
describe,
9+
el,
10+
expect,
11+
iit,
12+
inject,
13+
beforeEachProviders,
14+
it,
15+
xit
16+
} from 'angular2/testing_internal';
17+
18+
import {recognize} from 'angular2/src/alt_router/recognize';
19+
import {Routes, Route} from 'angular2/alt_router';
20+
import {provide, Component, ComponentResolver} from 'angular2/core';
21+
import {UrlSegment, Tree} from 'angular2/src/alt_router/segments';
22+
23+
export function main() {
24+
describe('recognize', () => {
25+
it('should handle position args',
26+
inject([AsyncTestCompleter, ComponentResolver], (async, resolver) => {
27+
recognize(resolver, ComponentA, tree(["b", "paramB", "c", "paramC"]))
28+
.then(r => {
29+
let b = r.root;
30+
expect(stringifyUrl(b.urlSegments)).toEqual(["b", "paramB"]);
31+
expect(b.type).toBe(ComponentB);
32+
33+
let c = r.firstChild(r.root);
34+
expect(stringifyUrl(c.urlSegments)).toEqual(["c", "paramC"]);
35+
expect(c.type).toBe(ComponentC);
36+
37+
async.done();
38+
});
39+
}));
40+
41+
it('should error when no matching routes',
42+
inject([AsyncTestCompleter, ComponentResolver], (async, resolver) => {
43+
recognize(resolver, ComponentA, tree(["invalid"]))
44+
.catch(e => {
45+
expect(e.message).toEqual("Cannot match any routes");
46+
async.done();
47+
});
48+
}));
49+
50+
it("should error when a component doesn't have @Routes",
51+
inject([AsyncTestCompleter, ComponentResolver], (async, resolver) => {
52+
recognize(resolver, ComponentA, tree(["d", "invalid"]))
53+
.catch(e => {
54+
expect(e.message)
55+
.toEqual("Component 'ComponentD' does not have route configuration");
56+
async.done();
57+
});
58+
}));
59+
});
60+
}
61+
62+
function tree(nodes: string[]) {
63+
return new Tree<UrlSegment>(nodes.map(v => new UrlSegment(v, {}, "")));
64+
}
65+
66+
function stringifyUrl(segments: UrlSegment[]): string[] {
67+
return segments.map(s => s.segment);
68+
}
69+
70+
@Component({selector: 'c', template: 't'})
71+
class ComponentC {
72+
}
73+
74+
@Component({selector: 'd', template: 't'})
75+
class ComponentD {
76+
}
77+
78+
@Component({selector: 'b', template: 't'})
79+
@Routes([new Route({path: "c/:c", component: ComponentC})])
80+
class ComponentB {
81+
}
82+
83+
@Component({selector: 'a', template: 't'})
84+
@Routes([
85+
new Route({path: "b/:b", component: ComponentB}),
86+
new Route({path: "d", component: ComponentD})
87+
])
88+
class ComponentA {
89+
}

0 commit comments

Comments
 (0)
X Tutup