From 9b356d9b869ecf322d45f8c5bafd39ab5520b84e Mon Sep 17 00:00:00 2001 From: vsavkin Date: Mon, 6 Jun 2016 10:15:23 -0700 Subject: [PATCH] fix(router): traverse route config in depth-first order Closes #17 --- modules/@angular/router/src/recognize.ts | 67 +++++++++++-------- .../@angular/router/test/recognize.spec.ts | 32 +++++---- 2 files changed, 57 insertions(+), 42 deletions(-) diff --git a/modules/@angular/router/src/recognize.ts b/modules/@angular/router/src/recognize.ts index d95878caef..f875ffcbb7 100644 --- a/modules/@angular/router/src/recognize.ts +++ b/modules/@angular/router/src/recognize.ts @@ -7,6 +7,8 @@ import { RouterConfig, Route } from './config'; import { Type } from '@angular/core'; import { Observable } from 'rxjs/Observable'; +class CannotRecognize {} + export function recognize(rootComponentType: Type, config: RouterConfig, url: UrlTree): Observable { try { const match = new MatchResult(rootComponentType, config, [url.root], {}, url._root.children, [], PRIMARY_OUTLET, null, url.root); @@ -17,7 +19,11 @@ export function recognize(rootComponentType: Type, config: RouterConfig, url: Ur obs.complete(); }); } catch(e) { - return new Observable(obs => obs.error(e)); + if (e instanceof CannotRecognize) { + return new Observable(obs => obs.error(new Error("Cannot match any routes"))); + } else { + return new Observable(obs => obs.error(e)); + } } } @@ -49,12 +55,21 @@ function createActivatedRouteSnapshot(match: MatchResult): ActivatedRouteSnapsho } function recognizeOne(config: Route[], url: TreeNode): TreeNode[] { - const m = match(config, url); - const primary = constructActivatedRoute(m); - const secondary = recognizeMany(config, m.secondary); - const res = primary.concat(secondary); - checkOutletNameUniqueness(res); - return res; + const matches = match(config, url); + for(let match of matches) { + try { + const primary = constructActivatedRoute(match); + const secondary = recognizeMany(config, match.secondary); + const res = primary.concat(secondary); + checkOutletNameUniqueness(res); + return res; + } catch (e) { + if (! (e instanceof CannotRecognize)) { + throw e; + } + } + } + throw new CannotRecognize(); } function checkOutletNameUniqueness(nodes: TreeNode[]): TreeNode[] { @@ -71,35 +86,29 @@ function checkOutletNameUniqueness(nodes: TreeNode[]): T return nodes; } -function match(config: Route[], url: TreeNode): MatchResult { - const m = matchNonIndex(config, url); - if (m) return m; - - const mIndex = matchIndex(config, [url], url.value); - if (mIndex) return mIndex; - - const availableRoutes = config.map(r => { - const outlet = !r.outlet ? '' : `${r.outlet}:`; - return `'${outlet}${r.path}'`; - }).join(", "); - throw new Error( - `Cannot match any routes. Current segment: '${url.value}'. Available routes: [${availableRoutes}].`); +function match(config: Route[], url: TreeNode): MatchResult[] { + const res = []; + for (let r of config) { + if (r.index) { + res.push(createIndexMatch(r, [url], url.value)); + } else { + const m = matchWithParts(r, url); + if (m) res.push(m); + } + } + return res; } -function matchNonIndex(config: Route[], url: TreeNode): MatchResult | null { - for (let r of config) { - let m = matchWithParts(r, url); - if (m) return m; - } - return null; +function createIndexMatch(r: Route, leftOverUrls:TreeNode[], lastUrlSegment:UrlSegment): MatchResult { + const outlet = r.outlet ? r.outlet : PRIMARY_OUTLET; + const children = r.children ? r.children : []; + return new MatchResult(r.component, children, [], lastUrlSegment.parameters, leftOverUrls, [], outlet, r, lastUrlSegment); } function matchIndex(config: Route[], leftOverUrls: TreeNode[], lastUrlSegment: UrlSegment): MatchResult | null { for (let r of config) { if (r.index) { - const outlet = r.outlet ? r.outlet : PRIMARY_OUTLET; - const children = r.children ? r.children : []; - return new MatchResult(r.component, children, [], lastUrlSegment.parameters, leftOverUrls, [], outlet, r, lastUrlSegment); + return createIndexMatch(r, leftOverUrls, lastUrlSegment); } } return null; diff --git a/modules/@angular/router/test/recognize.spec.ts b/modules/@angular/router/test/recognize.spec.ts index 04a1078790..bc368987b0 100644 --- a/modules/@angular/router/test/recognize.spec.ts +++ b/modules/@angular/router/test/recognize.spec.ts @@ -17,19 +17,6 @@ describe('recognize', () => { }); }); - it('should handle position args', () => { - recognize(RootComponent, [ - { - path: 'a/:id', component: ComponentA, children: [ - { path: 'b/:id', component: ComponentB} - ] - } - ], tree("a/paramA/b/paramB")).forEach(s => { - checkActivatedRoute(s.root, "", {}, RootComponent); - checkActivatedRoute(s.firstChild(s.root), "a/paramA", {id: 'paramA'}, ComponentA); - checkActivatedRoute(s.firstChild(s.firstChild(s.root)), "b/paramB", {id: 'paramB'}, ComponentB); - }); - }); it('should support secondary routes', () => { recognize(RootComponent, [ @@ -44,6 +31,25 @@ describe('recognize', () => { }); }); + it('should match routes in the depth first order', () => { + recognize(RootComponent, [ + {path: 'a', component: ComponentA, children: [{path: ':id', component: ComponentB}]}, + {path: 'a/:id', component: ComponentC} + ], tree("a/paramA")).forEach(s => { + checkActivatedRoute(s.root, "", {}, RootComponent); + checkActivatedRoute(s.firstChild(s.root), "a", {}, ComponentA); + checkActivatedRoute(s.firstChild(s.firstChild(s.root)), "paramA", {id: 'paramA'}, ComponentB); + }); + + recognize(RootComponent, [ + {path: 'a', component: ComponentA}, + {path: 'a/:id', component: ComponentC} + ], tree("a/paramA")).forEach(s => { + checkActivatedRoute(s.root, "", {}, RootComponent); + checkActivatedRoute(s.firstChild(s.root), "a/paramA", {id: 'paramA'}, ComponentC); + }); + }); + it('should use outlet name when matching secondary routes', () => { recognize(RootComponent, [ { path: 'a', component: ComponentA },