fix(router): sort possible routes by cost
This commit is contained in:
parent
8b6fa1cf19
commit
17392f663f
|
@ -20,15 +20,18 @@ export class Instruction {
|
|||
matchedUrl:string;
|
||||
params:Map<string, string>;
|
||||
reuse:boolean;
|
||||
cost:number;
|
||||
|
||||
constructor({params, component, children, matchedUrl}:{params:StringMap, component:any, children:Map, matchedUrl:string} = {}) {
|
||||
constructor({params, component, children, matchedUrl, parentCost}:{params:StringMap, component:any, children:Map, matchedUrl:string, cost:int} = {}) {
|
||||
this.reuse = false;
|
||||
this.matchedUrl = matchedUrl;
|
||||
this.cost = parentCost;
|
||||
if (isPresent(children)) {
|
||||
this._children = children;
|
||||
var childUrl;
|
||||
StringMapWrapper.forEach(this._children, (child, _) => {
|
||||
childUrl = child.matchedUrl;
|
||||
this.cost += child.cost;
|
||||
});
|
||||
if (isPresent(childUrl)) {
|
||||
this.matchedUrl += childUrl;
|
||||
|
|
|
@ -52,7 +52,7 @@ class StarSegment {
|
|||
var paramMatcher = RegExpWrapper.create("^:([^\/]+)$");
|
||||
var wildcardMatcher = RegExpWrapper.create("^\\*([^\/]+)$");
|
||||
|
||||
function parsePathString(route:string):List {
|
||||
function parsePathString(route:string) {
|
||||
// normalize route as not starting with a "/". Recognition will
|
||||
// also normalize.
|
||||
if (route[0] === "/") {
|
||||
|
@ -61,21 +61,25 @@ function parsePathString(route:string):List {
|
|||
|
||||
var segments = splitBySlash(route);
|
||||
var results = ListWrapper.create();
|
||||
var cost = 0;
|
||||
|
||||
for (var i=0; i<segments.length; i++) {
|
||||
var segment = segments[i],
|
||||
match;
|
||||
match;
|
||||
|
||||
if (isPresent(match = RegExpWrapper.firstMatch(paramMatcher, segment))) {
|
||||
ListWrapper.push(results, new DynamicSegment(match[1]));
|
||||
cost += 100;
|
||||
} else if (isPresent(match = RegExpWrapper.firstMatch(wildcardMatcher, segment))) {
|
||||
ListWrapper.push(results, new StarSegment(match[1]));
|
||||
cost += 10000;
|
||||
} else if (segment.length > 0) {
|
||||
ListWrapper.push(results, new StaticSegment(segment));
|
||||
cost += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
return {segments: results, cost};
|
||||
}
|
||||
|
||||
var SLASH_RE = RegExpWrapper.create('/');
|
||||
|
@ -89,12 +93,17 @@ export class PathRecognizer {
|
|||
segments:List;
|
||||
regex:RegExp;
|
||||
handler:any;
|
||||
cost:number;
|
||||
|
||||
constructor(path:string, handler:any) {
|
||||
this.handler = handler;
|
||||
this.segments = ListWrapper.create();
|
||||
|
||||
var segments = parsePathString(path);
|
||||
// TODO: use destructuring assignment
|
||||
// see https://github.com/angular/ts2dart/issues/158
|
||||
var parsed = parsePathString(path);
|
||||
var cost = parsed['cost'];
|
||||
var segments = parsed['segments'];
|
||||
var regexString = '^';
|
||||
|
||||
ListWrapper.forEach(segments, (segment) => {
|
||||
|
@ -103,6 +112,7 @@ export class PathRecognizer {
|
|||
|
||||
this.regex = RegExpWrapper.create(regexString);
|
||||
this.segments = segments;
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
parseParams(url:string):StringMap {
|
||||
|
|
|
@ -39,6 +39,7 @@ export class RouteRecognizer {
|
|||
var match;
|
||||
if (isPresent(match = RegExpWrapper.firstMatch(regex, url))) {
|
||||
var solution = StringMapWrapper.create();
|
||||
StringMapWrapper.set(solution, 'cost', pathRecognizer.cost);
|
||||
StringMapWrapper.set(solution, 'handler', pathRecognizer.handler);
|
||||
StringMapWrapper.set(solution, 'params', pathRecognizer.parseParams(url));
|
||||
|
||||
|
|
|
@ -77,39 +77,46 @@ export class RouteRegistry {
|
|||
return null;
|
||||
}
|
||||
|
||||
var solutions = componentRecognizer.recognize(url);
|
||||
var componentSolutions = componentRecognizer.recognize(url);
|
||||
var fullSolutions = ListWrapper.create();
|
||||
|
||||
for(var i = 0; i < solutions.length; i++) {
|
||||
var candidate = solutions[i];
|
||||
for(var i = 0; i < componentSolutions.length; i++) {
|
||||
var candidate = componentSolutions[i];
|
||||
if (candidate['unmatchedUrl'].length == 0) {
|
||||
return handlerToLeafInstructions(candidate, parentComponent);
|
||||
}
|
||||
ListWrapper.push(fullSolutions, handlerToLeafInstructions(candidate, parentComponent));
|
||||
} else {
|
||||
var children = StringMapWrapper.create(),
|
||||
allMapped = true;
|
||||
|
||||
var children = StringMapWrapper.create(),
|
||||
allMapped = true;
|
||||
|
||||
StringMapWrapper.forEach(candidate['handler']['components'], (component, name) => {
|
||||
if (!allMapped) {
|
||||
return;
|
||||
}
|
||||
var childInstruction = this.recognize(candidate['unmatchedUrl'], component);
|
||||
if (isPresent(childInstruction)) {
|
||||
childInstruction.params = candidate['params'];
|
||||
children[name] = childInstruction;
|
||||
} else {
|
||||
allMapped = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (allMapped) {
|
||||
return new Instruction({
|
||||
component: parentComponent,
|
||||
children: children,
|
||||
matchedUrl: candidate['matchedUrl']
|
||||
StringMapWrapper.forEach(candidate['handler']['components'], (component, name) => {
|
||||
if (!allMapped) {
|
||||
return;
|
||||
}
|
||||
var childInstruction = this.recognize(candidate['unmatchedUrl'], component);
|
||||
if (isPresent(childInstruction)) {
|
||||
childInstruction.params = candidate['params'];
|
||||
children[name] = childInstruction;
|
||||
} else {
|
||||
allMapped = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (allMapped) {
|
||||
ListWrapper.push(fullSolutions, new Instruction({
|
||||
component: parentComponent,
|
||||
children: children,
|
||||
matchedUrl: candidate['matchedUrl'],
|
||||
parentCost: candidate['cost']
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fullSolutions.length > 0) {
|
||||
ListWrapper.sort(fullSolutions, (a, b) => a.cost < b.cost ? -1 : 1);
|
||||
return fullSolutions[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -127,13 +134,15 @@ function handlerToLeafInstructions(context, parentComponent) {
|
|||
StringMapWrapper.forEach(context['handler']['components'], (component, outletName) => {
|
||||
children[outletName] = new Instruction({
|
||||
component: component,
|
||||
params: context['params']
|
||||
params: context['params'],
|
||||
parentCost: 0
|
||||
});
|
||||
});
|
||||
return new Instruction({
|
||||
component: parentComponent,
|
||||
children: children,
|
||||
matchedUrl: context['matchedUrl']
|
||||
matchedUrl: context['matchedUrl'],
|
||||
parentCost: context['cost']
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ export function main() {
|
|||
recognizer.addConfig('/test', handler);
|
||||
|
||||
expect(recognizer.recognize('/test')[0]).toEqual({
|
||||
'cost' : 1,
|
||||
'handler': { 'components': { 'a': 'b' } },
|
||||
'params': {},
|
||||
'matchedUrl': '/test',
|
||||
|
@ -34,6 +35,7 @@ export function main() {
|
|||
recognizer.addConfig('/', handler);
|
||||
|
||||
expect(recognizer.recognize('/')[0]).toEqual({
|
||||
'cost': 0,
|
||||
'handler': { 'components': { 'a': 'b' } },
|
||||
'params': {},
|
||||
'matchedUrl': '/',
|
||||
|
@ -44,6 +46,7 @@ export function main() {
|
|||
it('should work with a dynamic segment', () => {
|
||||
recognizer.addConfig('/user/:name', handler);
|
||||
expect(recognizer.recognize('/user/brian')[0]).toEqual({
|
||||
'cost': 101,
|
||||
'handler': handler,
|
||||
'params': { 'name': 'brian' },
|
||||
'matchedUrl': '/user/brian',
|
||||
|
@ -57,6 +60,7 @@ export function main() {
|
|||
var solutions = recognizer.recognize('/a');
|
||||
expect(solutions.length).toBe(1);
|
||||
expect(solutions[0]).toEqual({
|
||||
'cost': 1,
|
||||
'handler': handler,
|
||||
'params': {},
|
||||
'matchedUrl': '/b',
|
||||
|
|
|
@ -27,6 +27,24 @@ export function main() {
|
|||
expect(instruction.getChildInstruction('default').component).toBe(DummyCompB);
|
||||
});
|
||||
|
||||
it('should prefer static segments to dynamic', () => {
|
||||
registry.config(rootHostComponent, {'path': '/:site', 'component': DummyCompB});
|
||||
registry.config(rootHostComponent, {'path': '/home', 'component': DummyCompA});
|
||||
|
||||
var instruction = registry.recognize('/home', rootHostComponent);
|
||||
|
||||
expect(instruction.getChildInstruction('default').component).toBe(DummyCompA);
|
||||
});
|
||||
|
||||
it('should prefer dynamic segments to star', () => {
|
||||
registry.config(rootHostComponent, {'path': '/:site', 'component': DummyCompA});
|
||||
registry.config(rootHostComponent, {'path': '/*site', 'component': DummyCompB});
|
||||
|
||||
var instruction = registry.recognize('/home', rootHostComponent);
|
||||
|
||||
expect(instruction.getChildInstruction('default').component).toBe(DummyCompA);
|
||||
});
|
||||
|
||||
it('should match the full URL recursively', () => {
|
||||
registry.config(rootHostComponent, {'path': '/first', 'component': DummyParentComp});
|
||||
|
||||
|
|
Loading…
Reference in New Issue