diff --git a/modules/angular2/src/router/instruction.js b/modules/angular2/src/router/instruction.js index bc116987f9..0846c4ac21 100644 --- a/modules/angular2/src/router/instruction.js +++ b/modules/angular2/src/router/instruction.js @@ -20,15 +20,18 @@ export class Instruction { matchedUrl:string; params:Map; 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; diff --git a/modules/angular2/src/router/path_recognizer.js b/modules/angular2/src/router/path_recognizer.js index 6e9ea101aa..ac1fff5188 100644 --- a/modules/angular2/src/router/path_recognizer.js +++ b/modules/angular2/src/router/path_recognizer.js @@ -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 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 { diff --git a/modules/angular2/src/router/route_recognizer.js b/modules/angular2/src/router/route_recognizer.js index cf361237de..4598ddf9f6 100644 --- a/modules/angular2/src/router/route_recognizer.js +++ b/modules/angular2/src/router/route_recognizer.js @@ -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)); diff --git a/modules/angular2/src/router/route_registry.js b/modules/angular2/src/router/route_registry.js index 2bf5d2065f..5ac6af9e3a 100644 --- a/modules/angular2/src/router/route_registry.js +++ b/modules/angular2/src/router/route_registry.js @@ -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'] }); } diff --git a/modules/angular2/test/router/route_recognizer_spec.js b/modules/angular2/test/router/route_recognizer_spec.js index a35f54050a..96ddf24cda 100644 --- a/modules/angular2/test/router/route_recognizer_spec.js +++ b/modules/angular2/test/router/route_recognizer_spec.js @@ -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', diff --git a/modules/angular2/test/router/route_registry_spec.js b/modules/angular2/test/router/route_registry_spec.js index f4b9eb8a03..813e908915 100644 --- a/modules/angular2/test/router/route_registry_spec.js +++ b/modules/angular2/test/router/route_registry_spec.js @@ -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});