diff --git a/modules/@angular/router/index.ts b/modules/@angular/router/index.ts index 7ef0315d0e..069d3f8a3c 100644 --- a/modules/@angular/router/index.ts +++ b/modules/@angular/router/index.ts @@ -19,4 +19,4 @@ export {RouterOutletMap} from './src/router_outlet_map'; export {provideRouter} from './src/router_providers'; export {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from './src/router_state'; export {PRIMARY_OUTLET, Params} from './src/shared'; -export {DefaultUrlSerializer, UrlPathWithParams, UrlSerializer, UrlTree} from './src/url_tree'; +export {DefaultUrlSerializer, UrlSegment, UrlSerializer, UrlTree} from './src/url_tree'; diff --git a/modules/@angular/router/src/apply_redirects.ts b/modules/@angular/router/src/apply_redirects.ts index bdc2a6e1a4..04ef62e489 100644 --- a/modules/@angular/router/src/apply_redirects.ts +++ b/modules/@angular/router/src/apply_redirects.ts @@ -19,176 +19,186 @@ import {EmptyError} from 'rxjs/util/EmptyError'; import {Route, Routes} from './config'; import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader'; import {PRIMARY_OUTLET} from './shared'; -import {UrlPathWithParams, UrlSegment, UrlTree} from './url_tree'; +import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree'; import {merge, waitForMap} from './utils/collection'; class NoMatch { - constructor(public segment: UrlSegment = null) {} + constructor(public segmentGroup: UrlSegmentGroup = null) {} } + class AbsoluteRedirect { - constructor(public paths: UrlPathWithParams[]) {} + constructor(public segments: UrlSegment[]) {} } -function noMatch(segment: UrlSegment): Observable { - return new Observable((obs: Observer) => obs.error(new NoMatch(segment))); +function noMatch(segmentGroup: UrlSegmentGroup): Observable { + return new Observable( + (obs: Observer) => obs.error(new NoMatch(segmentGroup))); } -function absoluteRedirect(newPaths: UrlPathWithParams[]): Observable { - return new Observable( - (obs: Observer) => obs.error(new AbsoluteRedirect(newPaths))); +function absoluteRedirect(segments: UrlSegment[]): Observable { + return new Observable( + (obs: Observer) => obs.error(new AbsoluteRedirect(segments))); } + export function applyRedirects( injector: Injector, configLoader: RouterConfigLoader, urlTree: UrlTree, config: Routes): Observable { - return expandSegment(injector, configLoader, config, urlTree.root, PRIMARY_OUTLET) - .map(rootSegment => createUrlTree(urlTree, rootSegment)) + return expandSegmentGroup(injector, configLoader, config, urlTree.root, PRIMARY_OUTLET) + .map(rootSegmentGroup => createUrlTree(urlTree, rootSegmentGroup)) .catch(e => { if (e instanceof AbsoluteRedirect) { return of (createUrlTree( - urlTree, new UrlSegment([], {[PRIMARY_OUTLET]: new UrlSegment(e.paths, {})}))); + urlTree, + new UrlSegmentGroup([], {[PRIMARY_OUTLET]: new UrlSegmentGroup(e.segments, {})}))); } else if (e instanceof NoMatch) { - throw new Error(`Cannot match any routes: '${e.segment}'`); + throw new Error(`Cannot match any routes: '${e.segmentGroup}'`); } else { throw e; } }); } -function createUrlTree(urlTree: UrlTree, rootCandidate: UrlSegment): UrlTree { - const root = rootCandidate.pathsWithParams.length > 0 ? - new UrlSegment([], {[PRIMARY_OUTLET]: rootCandidate}) : +function createUrlTree(urlTree: UrlTree, rootCandidate: UrlSegmentGroup): UrlTree { + const root = rootCandidate.segments.length > 0 ? + new UrlSegmentGroup([], {[PRIMARY_OUTLET]: rootCandidate}) : rootCandidate; return new UrlTree(root, urlTree.queryParams, urlTree.fragment); } -function expandSegment( - injector: Injector, configLoader: RouterConfigLoader, routes: Route[], segment: UrlSegment, - outlet: string): Observable { - if (segment.pathsWithParams.length === 0 && segment.hasChildren()) { - return expandSegmentChildren(injector, configLoader, routes, segment) - .map(children => new UrlSegment([], children)); +function expandSegmentGroup( + injector: Injector, configLoader: RouterConfigLoader, routes: Route[], + segmentGroup: UrlSegmentGroup, outlet: string): Observable { + if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) { + return expandChildren(injector, configLoader, routes, segmentGroup) + .map(children => new UrlSegmentGroup([], children)); } else { - return expandPathsWithParams( - injector, configLoader, segment, routes, segment.pathsWithParams, outlet, true); + return expandSegment( + injector, configLoader, segmentGroup, routes, segmentGroup.segments, outlet, true); } } -function expandSegmentChildren( +function expandChildren( injector: Injector, configLoader: RouterConfigLoader, routes: Route[], - segment: UrlSegment): Observable<{[name: string]: UrlSegment}> { + segmentGroup: UrlSegmentGroup): Observable<{[name: string]: UrlSegmentGroup}> { return waitForMap( - segment.children, - (childOutlet, child) => expandSegment(injector, configLoader, routes, child, childOutlet)); + segmentGroup.children, (childOutlet, child) => expandSegmentGroup( + injector, configLoader, routes, child, childOutlet)); } -function expandPathsWithParams( - injector: Injector, configLoader: RouterConfigLoader, segment: UrlSegment, routes: Route[], - paths: UrlPathWithParams[], outlet: string, allowRedirects: boolean): Observable { - const processRoutes = - of (...routes) - .map(r => { - return expandPathsWithParamsAgainstRoute( - injector, configLoader, segment, routes, r, paths, outlet, allowRedirects) - .catch((e) => { - if (e instanceof NoMatch) - return of (null); - else - throw e; - }); - }) - .concatAll(); +function expandSegment( + injector: Injector, configLoader: RouterConfigLoader, segmentGroup: UrlSegmentGroup, + routes: Route[], segments: UrlSegment[], outlet: string, + allowRedirects: boolean): Observable { + const processRoutes = of (...routes) + .map(r => { + return expandSegmentAgainstRoute( + injector, configLoader, segmentGroup, routes, r, segments, + outlet, allowRedirects) + .catch((e) => { + if (e instanceof NoMatch) + return of (null); + else + throw e; + }); + }) + .concatAll(); - return processRoutes.first(s => !!s).catch((e: any, _: any): Observable => { + return processRoutes.first(s => !!s).catch((e: any, _: any): Observable => { if (e instanceof EmptyError) { - throw new NoMatch(segment); + throw new NoMatch(segmentGroup); } else { throw e; } }); } -function expandPathsWithParamsAgainstRoute( - injector: Injector, configLoader: RouterConfigLoader, segment: UrlSegment, routes: Route[], - route: Route, paths: UrlPathWithParams[], outlet: string, - allowRedirects: boolean): Observable { - if (getOutlet(route) !== outlet) return noMatch(segment); - if (route.redirectTo !== undefined && !allowRedirects) return noMatch(segment); +function expandSegmentAgainstRoute( + injector: Injector, configLoader: RouterConfigLoader, segmentGroup: UrlSegmentGroup, + routes: Route[], route: Route, paths: UrlSegment[], outlet: string, + allowRedirects: boolean): Observable { + if (getOutlet(route) !== outlet) return noMatch(segmentGroup); + if (route.redirectTo !== undefined && !allowRedirects) return noMatch(segmentGroup); if (route.redirectTo !== undefined) { - return expandPathsWithParamsAgainstRouteUsingRedirect( - injector, configLoader, segment, routes, route, paths, outlet); + return expandSegmentAgainstRouteUsingRedirect( + injector, configLoader, segmentGroup, routes, route, paths, outlet); } else { - return matchPathsWithParamsAgainstRoute(injector, configLoader, segment, route, paths); + return matchSegmentAgainstRoute(injector, configLoader, segmentGroup, route, paths); } } -function expandPathsWithParamsAgainstRouteUsingRedirect( - injector: Injector, configLoader: RouterConfigLoader, segment: UrlSegment, routes: Route[], - route: Route, paths: UrlPathWithParams[], outlet: string): Observable { +function expandSegmentAgainstRouteUsingRedirect( + injector: Injector, configLoader: RouterConfigLoader, segmentGroup: UrlSegmentGroup, + routes: Route[], route: Route, segments: UrlSegment[], + outlet: string): Observable { if (route.path === '**') { return expandWildCardWithParamsAgainstRouteUsingRedirect(route); } else { - return expandRegularPathWithParamsAgainstRouteUsingRedirect( - injector, configLoader, segment, routes, route, paths, outlet); + return expandRegularSegmentAgainstRouteUsingRedirect( + injector, configLoader, segmentGroup, routes, route, segments, outlet); } } -function expandWildCardWithParamsAgainstRouteUsingRedirect(route: Route): Observable { - const newPaths = applyRedirectCommands([], route.redirectTo, {}); +function expandWildCardWithParamsAgainstRouteUsingRedirect(route: Route): + Observable { + const newSegments = applyRedirectCommands([], route.redirectTo, {}); if (route.redirectTo.startsWith('/')) { - return absoluteRedirect(newPaths); + return absoluteRedirect(newSegments); } else { - return of (new UrlSegment(newPaths, {})); + return of (new UrlSegmentGroup(newSegments, {})); } } -function expandRegularPathWithParamsAgainstRouteUsingRedirect( - injector: Injector, configLoader: RouterConfigLoader, segment: UrlSegment, routes: Route[], - route: Route, paths: UrlPathWithParams[], outlet: string): Observable { - const {matched, consumedPaths, lastChild, positionalParamSegments} = match(segment, route, paths); - if (!matched) return noMatch(segment); +function expandRegularSegmentAgainstRouteUsingRedirect( + injector: Injector, configLoader: RouterConfigLoader, segmentGroup: UrlSegmentGroup, + routes: Route[], route: Route, segments: UrlSegment[], + outlet: string): Observable { + const {matched, consumedSegments, lastChild, positionalParamSegments} = + match(segmentGroup, route, segments); + if (!matched) return noMatch(segmentGroup); - const newPaths = - applyRedirectCommands(consumedPaths, route.redirectTo, positionalParamSegments); + const newSegments = + applyRedirectCommands(consumedSegments, route.redirectTo, positionalParamSegments); if (route.redirectTo.startsWith('/')) { - return absoluteRedirect(newPaths); + return absoluteRedirect(newSegments); } else { - return expandPathsWithParams( - injector, configLoader, segment, routes, newPaths.concat(paths.slice(lastChild)), outlet, - false); + return expandSegment( + injector, configLoader, segmentGroup, routes, newSegments.concat(segments.slice(lastChild)), + outlet, false); } } -function matchPathsWithParamsAgainstRoute( - injector: Injector, configLoader: RouterConfigLoader, rawSegment: UrlSegment, route: Route, - paths: UrlPathWithParams[]): Observable { +function matchSegmentAgainstRoute( + injector: Injector, configLoader: RouterConfigLoader, rawSegmentGroup: UrlSegmentGroup, + route: Route, segments: UrlSegment[]): Observable { if (route.path === '**') { - return of (new UrlSegment(paths, {})); + return of (new UrlSegmentGroup(segments, {})); } else { - const {matched, consumedPaths, lastChild} = match(rawSegment, route, paths); - if (!matched) return noMatch(rawSegment); + const {matched, consumedSegments, lastChild} = match(rawSegmentGroup, route, segments); + if (!matched) return noMatch(rawSegmentGroup); - const rawSlicedPath = paths.slice(lastChild); + const rawSlicedSegments = segments.slice(lastChild); return getChildConfig(injector, configLoader, route).mergeMap(routerConfig => { const childInjector = routerConfig.injector; const childConfig = routerConfig.routes; - const {segment, slicedPath} = split(rawSegment, consumedPaths, rawSlicedPath, childConfig); + const {segmentGroup, slicedSegments} = + split(rawSegmentGroup, consumedSegments, rawSlicedSegments, childConfig); - if (slicedPath.length === 0 && segment.hasChildren()) { - return expandSegmentChildren(childInjector, configLoader, childConfig, segment) - .map(children => new UrlSegment(consumedPaths, children)); + if (slicedSegments.length === 0 && segmentGroup.hasChildren()) { + return expandChildren(childInjector, configLoader, childConfig, segmentGroup) + .map(children => new UrlSegmentGroup(consumedSegments, children)); - } else if (childConfig.length === 0 && slicedPath.length === 0) { - return of (new UrlSegment(consumedPaths, {})); + } else if (childConfig.length === 0 && slicedSegments.length === 0) { + return of (new UrlSegmentGroup(consumedSegments, {})); } else { - return expandPathsWithParams( - childInjector, configLoader, segment, childConfig, slicedPath, PRIMARY_OUTLET, - true) - .map(cs => new UrlSegment(consumedPaths.concat(cs.pathsWithParams), cs.children)); + return expandSegment( + childInjector, configLoader, segmentGroup, childConfig, slicedSegments, + PRIMARY_OUTLET, true) + .map(cs => new UrlSegmentGroup(consumedSegments.concat(cs.segments), cs.children)); } }); } @@ -208,33 +218,33 @@ function getChildConfig(injector: Injector, configLoader: RouterConfigLoader, ro } } -function match(segment: UrlSegment, route: Route, paths: UrlPathWithParams[]): { +function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[]): { matched: boolean, - consumedPaths: UrlPathWithParams[], + consumedSegments: UrlSegment[], lastChild: number, - positionalParamSegments: {[k: string]: UrlPathWithParams} + positionalParamSegments: {[k: string]: UrlSegment} } { const noMatch = - {matched: false, consumedPaths: [], lastChild: 0, positionalParamSegments: {}}; + {matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}}; if (route.path === '') { if ((route.terminal || route.pathMatch === 'full') && - (segment.hasChildren() || paths.length > 0)) { - return {matched: false, consumedPaths: [], lastChild: 0, positionalParamSegments: {}}; + (segmentGroup.hasChildren() || segments.length > 0)) { + return {matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}}; } else { - return {matched: true, consumedPaths: [], lastChild: 0, positionalParamSegments: {}}; + return {matched: true, consumedSegments: [], lastChild: 0, positionalParamSegments: {}}; } } const path = route.path; const parts = path.split('/'); - const positionalParamSegments: {[k: string]: UrlPathWithParams} = {}; - const consumedPaths: UrlPathWithParams[] = []; + const positionalParamSegments: {[k: string]: UrlSegment} = {}; + const consumedSegments: UrlSegment[] = []; let currentIndex = 0; for (let i = 0; i < parts.length; ++i) { - if (currentIndex >= paths.length) return noMatch; - const current = paths[currentIndex]; + if (currentIndex >= segments.length) return noMatch; + const current = segments[currentIndex]; const p = parts[i]; const isPosParam = p.startsWith(':'); @@ -243,128 +253,131 @@ function match(segment: UrlSegment, route: Route, paths: UrlPathWithParams[]): { if (isPosParam) { positionalParamSegments[p.substring(1)] = current; } - consumedPaths.push(current); + consumedSegments.push(current); currentIndex++; } - if (route.terminal && (segment.hasChildren() || currentIndex < paths.length)) { - return {matched: false, consumedPaths: [], lastChild: 0, positionalParamSegments: {}}; + if (route.terminal && (segmentGroup.hasChildren() || currentIndex < segments.length)) { + return {matched: false, consumedSegments: [], lastChild: 0, positionalParamSegments: {}}; } - return {matched: true, consumedPaths, lastChild: currentIndex, positionalParamSegments}; + return {matched: true, consumedSegments, lastChild: currentIndex, positionalParamSegments}; } function applyRedirectCommands( - paths: UrlPathWithParams[], redirectTo: string, - posParams: {[k: string]: UrlPathWithParams}): UrlPathWithParams[] { + segments: UrlSegment[], redirectTo: string, + posParams: {[k: string]: UrlSegment}): UrlSegment[] { const r = redirectTo.startsWith('/') ? redirectTo.substring(1) : redirectTo; if (r === '') { return []; } else { - return createPaths(redirectTo, r.split('/'), paths, posParams); + return createSegments(redirectTo, r.split('/'), segments, posParams); } } -function createPaths( - redirectTo: string, parts: string[], segments: UrlPathWithParams[], - posParams: {[k: string]: UrlPathWithParams}): UrlPathWithParams[] { +function createSegments( + redirectTo: string, parts: string[], segments: UrlSegment[], + posParams: {[k: string]: UrlSegment}): UrlSegment[] { return parts.map( p => p.startsWith(':') ? findPosParam(p, posParams, redirectTo) : - findOrCreatePath(p, segments)); + findOrCreateSegment(p, segments)); } function findPosParam( - part: string, posParams: {[k: string]: UrlPathWithParams}, - redirectTo: string): UrlPathWithParams { + part: string, posParams: {[k: string]: UrlSegment}, redirectTo: string): UrlSegment { const paramName = part.substring(1); const pos = posParams[paramName]; if (!pos) throw new Error(`Cannot redirect to '${redirectTo}'. Cannot find '${part}'.`); return pos; } -function findOrCreatePath(part: string, paths: UrlPathWithParams[]): UrlPathWithParams { +function findOrCreateSegment(part: string, segments: UrlSegment[]): UrlSegment { let idx = 0; - for (const s of paths) { + for (const s of segments) { if (s.path === part) { - paths.splice(idx); + segments.splice(idx); return s; } idx++; } - return new UrlPathWithParams(part, {}); + return new UrlSegment(part, {}); } function split( - segment: UrlSegment, consumedPaths: UrlPathWithParams[], slicedPath: UrlPathWithParams[], + segmentGroup: UrlSegmentGroup, consumedSegments: UrlSegment[], slicedSegments: UrlSegment[], config: Route[]) { - if (slicedPath.length > 0 && - containsEmptyPathRedirectsWithNamedOutlets(segment, slicedPath, config)) { - const s = new UrlSegment( - consumedPaths, - createChildrenForEmptyPaths(config, new UrlSegment(slicedPath, segment.children))); - return {segment: mergeTrivialChildren(s), slicedPath: []}; + if (slicedSegments.length > 0 && + containsEmptyPathRedirectsWithNamedOutlets(segmentGroup, slicedSegments, config)) { + const s = new UrlSegmentGroup( + consumedSegments, createChildrenForEmptySegments( + config, new UrlSegmentGroup(slicedSegments, segmentGroup.children))); + return {segmentGroup: mergeTrivialChildren(s), slicedSegments: []}; - } else if (slicedPath.length === 0 && containsEmptyPathRedirects(segment, slicedPath, config)) { - const s = new UrlSegment( - segment.pathsWithParams, - addEmptyPathsToChildrenIfNeeded(segment, slicedPath, config, segment.children)); - return {segment: mergeTrivialChildren(s), slicedPath}; + } else if ( + slicedSegments.length === 0 && + containsEmptyPathRedirects(segmentGroup, slicedSegments, config)) { + const s = new UrlSegmentGroup( + segmentGroup.segments, addEmptySegmentsToChildrenIfNeeded( + segmentGroup, slicedSegments, config, segmentGroup.children)); + return {segmentGroup: mergeTrivialChildren(s), slicedSegments}; } else { - return {segment, slicedPath}; + return {segmentGroup, slicedSegments}; } } -function mergeTrivialChildren(s: UrlSegment): UrlSegment { +function mergeTrivialChildren(s: UrlSegmentGroup): UrlSegmentGroup { if (s.numberOfChildren === 1 && s.children[PRIMARY_OUTLET]) { const c = s.children[PRIMARY_OUTLET]; - return new UrlSegment(s.pathsWithParams.concat(c.pathsWithParams), c.children); + return new UrlSegmentGroup(s.segments.concat(c.segments), c.children); } else { return s; } } -function addEmptyPathsToChildrenIfNeeded( - segment: UrlSegment, slicedPath: UrlPathWithParams[], routes: Route[], - children: {[name: string]: UrlSegment}): {[name: string]: UrlSegment} { - const res: {[name: string]: UrlSegment} = {}; +function addEmptySegmentsToChildrenIfNeeded( + segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], routes: Route[], + children: {[name: string]: UrlSegmentGroup}): {[name: string]: UrlSegmentGroup} { + const res: {[name: string]: UrlSegmentGroup} = {}; for (let r of routes) { - if (emptyPathRedirect(segment, slicedPath, r) && !children[getOutlet(r)]) { - res[getOutlet(r)] = new UrlSegment([], {}); + if (emptyPathRedirect(segmentGroup, slicedSegments, r) && !children[getOutlet(r)]) { + res[getOutlet(r)] = new UrlSegmentGroup([], {}); } } return merge(children, res); } -function createChildrenForEmptyPaths( - routes: Route[], primarySegment: UrlSegment): {[name: string]: UrlSegment} { - const res: {[name: string]: UrlSegment} = {}; - res[PRIMARY_OUTLET] = primarySegment; +function createChildrenForEmptySegments( + routes: Route[], primarySegmentGroup: UrlSegmentGroup): {[name: string]: UrlSegmentGroup} { + const res: {[name: string]: UrlSegmentGroup} = {}; + res[PRIMARY_OUTLET] = primarySegmentGroup; for (let r of routes) { if (r.path === '' && getOutlet(r) !== PRIMARY_OUTLET) { - res[getOutlet(r)] = new UrlSegment([], {}); + res[getOutlet(r)] = new UrlSegmentGroup([], {}); } } return res; } function containsEmptyPathRedirectsWithNamedOutlets( - segment: UrlSegment, slicedPath: UrlPathWithParams[], routes: Route[]): boolean { + segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], routes: Route[]): boolean { return routes .filter( - r => emptyPathRedirect(segment, slicedPath, r) && getOutlet(r) !== PRIMARY_OUTLET) + r => emptyPathRedirect(segmentGroup, slicedSegments, r) && + getOutlet(r) !== PRIMARY_OUTLET) .length > 0; } function containsEmptyPathRedirects( - segment: UrlSegment, slicedPath: UrlPathWithParams[], routes: Route[]): boolean { - return routes.filter(r => emptyPathRedirect(segment, slicedPath, r)).length > 0; + segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], routes: Route[]): boolean { + return routes.filter(r => emptyPathRedirect(segmentGroup, slicedSegments, r)).length > 0; } function emptyPathRedirect( - segment: UrlSegment, slicedPath: UrlPathWithParams[], r: Route): boolean { - if ((segment.hasChildren() || slicedPath.length > 0) && (r.terminal || r.pathMatch === 'full')) + segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], r: Route): boolean { + if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && + (r.terminal || r.pathMatch === 'full')) return false; return r.path === '' && r.redirectTo !== undefined; } diff --git a/modules/@angular/router/src/create_url_tree.ts b/modules/@angular/router/src/create_url_tree.ts index b105204258..6682128797 100644 --- a/modules/@angular/router/src/create_url_tree.ts +++ b/modules/@angular/router/src/create_url_tree.ts @@ -8,7 +8,7 @@ import {ActivatedRoute} from './router_state'; import {PRIMARY_OUTLET, Params} from './shared'; -import {UrlPathWithParams, UrlSegment, UrlTree} from './url_tree'; +import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree'; import {forEach, shallowEqual} from './utils/collection'; export function createUrlTree( @@ -22,15 +22,16 @@ export function createUrlTree( validateCommands(normalizedCommands); if (navigateToRoot(normalizedCommands)) { - return tree(urlTree.root, new UrlSegment([], {}), urlTree, queryParams, fragment); + return tree(urlTree.root, new UrlSegmentGroup([], {}), urlTree, queryParams, fragment); } const startingPosition = findStartingPosition(normalizedCommands, urlTree, route); - const segment = startingPosition.processChildren ? - updateSegmentChildren( - startingPosition.segment, startingPosition.index, normalizedCommands.commands) : - updateSegment(startingPosition.segment, startingPosition.index, normalizedCommands.commands); - return tree(startingPosition.segment, segment, urlTree, queryParams, fragment); + const segmentGroup = startingPosition.processChildren ? + updateSegmentGroupChildren( + startingPosition.segmentGroup, startingPosition.index, normalizedCommands.commands) : + updateSegmentGroup( + startingPosition.segmentGroup, startingPosition.index, normalizedCommands.commands); + return tree(startingPosition.segmentGroup, segmentGroup, urlTree, queryParams, fragment); } function validateCommands(n: NormalizedNavigationCommands): void { @@ -40,27 +41,29 @@ function validateCommands(n: NormalizedNavigationCommands): void { } function tree( - oldSegment: UrlSegment, newSegment: UrlSegment, urlTree: UrlTree, queryParams: Params, - fragment: string): UrlTree { - if (urlTree.root === oldSegment) { - return new UrlTree(newSegment, stringify(queryParams), fragment); + oldSegmentGroup: UrlSegmentGroup, newSegmentGroup: UrlSegmentGroup, urlTree: UrlTree, + queryParams: Params, fragment: string): UrlTree { + if (urlTree.root === oldSegmentGroup) { + return new UrlTree(newSegmentGroup, stringify(queryParams), fragment); } else { return new UrlTree( - replaceSegment(urlTree.root, oldSegment, newSegment), stringify(queryParams), fragment); + replaceSegment(urlTree.root, oldSegmentGroup, newSegmentGroup), stringify(queryParams), + fragment); } } function replaceSegment( - current: UrlSegment, oldSegment: UrlSegment, newSegment: UrlSegment): UrlSegment { - const children: {[key: string]: UrlSegment} = {}; - forEach(current.children, (c: UrlSegment, outletName: string) => { + current: UrlSegmentGroup, oldSegment: UrlSegmentGroup, + newSegment: UrlSegmentGroup): UrlSegmentGroup { + const children: {[key: string]: UrlSegmentGroup} = {}; + forEach(current.children, (c: UrlSegmentGroup, outletName: string) => { if (c === oldSegment) { children[outletName] = newSegment; } else { children[outletName] = replaceSegment(c, oldSegment, newSegment); } }); - return new UrlSegment(current.pathsWithParams, children); + return new UrlSegmentGroup(current.segments, children); } function navigateToRoot(normalizedChange: NormalizedNavigationCommands): boolean { @@ -131,7 +134,9 @@ function normalizeCommands(commands: any[]): NormalizedNavigationCommands { } class Position { - constructor(public segment: UrlSegment, public processChildren: boolean, public index: number) {} + constructor( + public segmentGroup: UrlSegmentGroup, public processChildren: boolean, public index: number) { + } } function findStartingPosition( @@ -160,58 +165,59 @@ function getOutlets(commands: any[]): {[k: string]: any[]} { return commands[0].outlets; } -function updateSegment(segment: UrlSegment, startIndex: number, commands: any[]): UrlSegment { - if (!segment) { - segment = new UrlSegment([], {}); +function updateSegmentGroup( + segmentGroup: UrlSegmentGroup, startIndex: number, commands: any[]): UrlSegmentGroup { + if (!segmentGroup) { + segmentGroup = new UrlSegmentGroup([], {}); } - if (segment.pathsWithParams.length === 0 && segment.hasChildren()) { - return updateSegmentChildren(segment, startIndex, commands); + if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) { + return updateSegmentGroupChildren(segmentGroup, startIndex, commands); } - const m = prefixedWith(segment, startIndex, commands); + const m = prefixedWith(segmentGroup, startIndex, commands); const slicedCommands = commands.slice(m.lastIndex); if (m.match && slicedCommands.length === 0) { - return new UrlSegment(segment.pathsWithParams, {}); - } else if (m.match && !segment.hasChildren()) { - return createNewSegment(segment, startIndex, commands); + return new UrlSegmentGroup(segmentGroup.segments, {}); + } else if (m.match && !segmentGroup.hasChildren()) { + return createNewSegmentGroup(segmentGroup, startIndex, commands); } else if (m.match) { - return updateSegmentChildren(segment, 0, slicedCommands); + return updateSegmentGroupChildren(segmentGroup, 0, slicedCommands); } else { - return createNewSegment(segment, startIndex, commands); + return createNewSegmentGroup(segmentGroup, startIndex, commands); } } -function updateSegmentChildren( - segment: UrlSegment, startIndex: number, commands: any[]): UrlSegment { +function updateSegmentGroupChildren( + segmentGroup: UrlSegmentGroup, startIndex: number, commands: any[]): UrlSegmentGroup { if (commands.length === 0) { - return new UrlSegment(segment.pathsWithParams, {}); + return new UrlSegmentGroup(segmentGroup.segments, {}); } else { const outlets = getOutlets(commands); - const children: {[key: string]: UrlSegment} = {}; + const children: {[key: string]: UrlSegmentGroup} = {}; forEach(outlets, (commands: any, outlet: string) => { if (commands !== null) { - children[outlet] = updateSegment(segment.children[outlet], startIndex, commands); + children[outlet] = updateSegmentGroup(segmentGroup.children[outlet], startIndex, commands); } }); - forEach(segment.children, (child: UrlSegment, childOutlet: string) => { + forEach(segmentGroup.children, (child: UrlSegmentGroup, childOutlet: string) => { if (outlets[childOutlet] === undefined) { children[childOutlet] = child; } }); - return new UrlSegment(segment.pathsWithParams, children); + return new UrlSegmentGroup(segmentGroup.segments, children); } } -function prefixedWith(segment: UrlSegment, startIndex: number, commands: any[]) { +function prefixedWith(segmentGroup: UrlSegmentGroup, startIndex: number, commands: any[]) { let currentCommandIndex = 0; let currentPathIndex = startIndex; const noMatch = {match: false, lastIndex: 0}; - while (currentPathIndex < segment.pathsWithParams.length) { + while (currentPathIndex < segmentGroup.segments.length) { if (currentCommandIndex >= commands.length) return noMatch; - const path = segment.pathsWithParams[currentPathIndex]; + const path = segmentGroup.segments[currentPathIndex]; const curr = getPath(commands[currentCommandIndex]); const next = currentCommandIndex < commands.length - 1 ? commands[currentCommandIndex + 1] : null; @@ -229,14 +235,15 @@ function prefixedWith(segment: UrlSegment, startIndex: number, commands: any[]) return {match: true, lastIndex: currentCommandIndex}; } -function createNewSegment(segment: UrlSegment, startIndex: number, commands: any[]): UrlSegment { - const paths = segment.pathsWithParams.slice(0, startIndex); +function createNewSegmentGroup( + segmentGroup: UrlSegmentGroup, startIndex: number, commands: any[]): UrlSegmentGroup { + const paths = segmentGroup.segments.slice(0, startIndex); let i = 0; while (i < commands.length) { // if we start with an object literal, we need to reuse the path part from the segment if (i === 0 && (typeof commands[0] === 'object')) { - const p = segment.pathsWithParams[startIndex]; - paths.push(new UrlPathWithParams(p.path, commands[0])); + const p = segmentGroup.segments[startIndex]; + paths.push(new UrlSegment(p.path, commands[0])); i++; continue; } @@ -244,14 +251,14 @@ function createNewSegment(segment: UrlSegment, startIndex: number, commands: any const curr = getPath(commands[i]); const next = (i < commands.length - 1) ? commands[i + 1] : null; if (curr && next && (typeof next === 'object')) { - paths.push(new UrlPathWithParams(curr, stringify(next))); + paths.push(new UrlSegment(curr, stringify(next))); i += 2; } else { - paths.push(new UrlPathWithParams(curr, {})); + paths.push(new UrlSegment(curr, {})); i++; } } - return new UrlSegment(paths, {}); + return new UrlSegmentGroup(paths, {}); } function stringify(params: {[key: string]: any}): {[key: string]: string} { @@ -260,7 +267,6 @@ function stringify(params: {[key: string]: any}): {[key: string]: string} { return res; } -function compare( - path: string, params: {[key: string]: any}, pathWithParams: UrlPathWithParams): boolean { - return path == pathWithParams.path && shallowEqual(params, pathWithParams.parameters); +function compare(path: string, params: {[key: string]: any}, segment: UrlSegment): boolean { + return path == segment.path && shallowEqual(params, segment.parameters); } \ No newline at end of file diff --git a/modules/@angular/router/src/recognize.ts b/modules/@angular/router/src/recognize.ts index 1b16418181..62630f0f74 100644 --- a/modules/@angular/router/src/recognize.ts +++ b/modules/@angular/router/src/recognize.ts @@ -14,12 +14,12 @@ import {of } from 'rxjs/observable/of'; import {Data, ResolveData, Route, Routes} from './config'; import {ActivatedRouteSnapshot, InheritedResolve, RouterStateSnapshot} from './router_state'; import {PRIMARY_OUTLET, Params} from './shared'; -import {UrlPathWithParams, UrlSegment, UrlTree, mapChildrenIntoArray} from './url_tree'; +import {UrlSegment, UrlSegmentGroup, UrlTree, mapChildrenIntoArray} from './url_tree'; import {last, merge} from './utils/collection'; import {TreeNode} from './utils/tree'; class NoMatch { - constructor(public segment: UrlSegment = null) {} + constructor(public segmentGroup: UrlSegmentGroup = null) {} } class InheritedFromParent { @@ -41,9 +41,9 @@ class InheritedFromParent { export function recognize(rootComponentType: Type, config: Routes, urlTree: UrlTree, url: string): Observable { try { - const rootSegment = split(urlTree.root, [], [], config).segment; - const children = - processSegment(config, rootSegment, InheritedFromParent.empty(null), PRIMARY_OUTLET); + const rootSegmentGroup = split(urlTree.root, [], [], config).segmentGroup; + const children = processSegmentGroup( + config, rootSegmentGroup, InheritedFromParent.empty(null), PRIMARY_OUTLET); const root = new ActivatedRouteSnapshot( [], Object.freeze({}), {}, PRIMARY_OUTLET, rootComponentType, null, urlTree.root, -1, InheritedResolve.empty); @@ -54,7 +54,7 @@ export function recognize(rootComponentType: Type, config: Routes, urlTree: UrlT if (e instanceof NoMatch) { return new Observable( (obs: Observer) => - obs.error(new Error(`Cannot match any routes: '${e.segment}'`))); + obs.error(new Error(`Cannot match any routes: '${e.segmentGroup}'`))); } else { return new Observable( (obs: Observer) => obs.error(e)); @@ -62,21 +62,22 @@ export function recognize(rootComponentType: Type, config: Routes, urlTree: UrlT } } -function processSegment( - config: Route[], segment: UrlSegment, inherited: InheritedFromParent, +function processSegmentGroup( + config: Route[], segmentGroup: UrlSegmentGroup, inherited: InheritedFromParent, outlet: string): TreeNode[] { - if (segment.pathsWithParams.length === 0 && segment.hasChildren()) { - return processSegmentChildren(config, segment, inherited); + if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) { + return processChildren(config, segmentGroup, inherited); } else { - return processPathsWithParams(config, segment, 0, segment.pathsWithParams, inherited, outlet); + return processSegment(config, segmentGroup, 0, segmentGroup.segments, inherited, outlet); } } -function processSegmentChildren( - config: Route[], segment: UrlSegment, +function processChildren( + config: Route[], segmentGroup: UrlSegmentGroup, inherited: InheritedFromParent): TreeNode[] { const children = mapChildrenIntoArray( - segment, (child, childOutlet) => processSegment(config, child, inherited, childOutlet)); + segmentGroup, + (child, childOutlet) => processSegmentGroup(config, child, inherited, childOutlet)); checkOutletNameUniqueness(children); sortActivatedRouteSnapshots(children); return children; @@ -90,21 +91,21 @@ function sortActivatedRouteSnapshots(nodes: TreeNode[]): }); } -function processPathsWithParams( - config: Route[], segment: UrlSegment, pathIndex: number, paths: UrlPathWithParams[], +function processSegment( + config: Route[], segmentGroup: UrlSegmentGroup, pathIndex: number, segments: UrlSegment[], inherited: InheritedFromParent, outlet: string): TreeNode[] { for (let r of config) { try { - return processPathsWithParamsAgainstRoute(r, segment, pathIndex, paths, inherited, outlet); + return processSegmentAgainstRoute(r, segmentGroup, pathIndex, segments, inherited, outlet); } catch (e) { if (!(e instanceof NoMatch)) throw e; } } - throw new NoMatch(segment); + throw new NoMatch(segmentGroup); } -function processPathsWithParamsAgainstRoute( - route: Route, rawSegment: UrlSegment, pathIndex: number, paths: UrlPathWithParams[], +function processSegmentAgainstRoute( + route: Route, rawSegment: UrlSegmentGroup, pathIndex: number, segments: UrlSegment[], inherited: InheritedFromParent, outlet: string): TreeNode[] { if (route.redirectTo) throw new NoMatch(); @@ -113,42 +114,44 @@ function processPathsWithParamsAgainstRoute( const newInheritedResolve = new InheritedResolve(inherited.resolve, getResolve(route)); if (route.path === '**') { - const params = paths.length > 0 ? last(paths).parameters : {}; + const params = segments.length > 0 ? last(segments).parameters : {}; const snapshot = new ActivatedRouteSnapshot( - paths, Object.freeze(merge(inherited.allParams, params)), + segments, Object.freeze(merge(inherited.allParams, params)), merge(inherited.allData, getData(route)), outlet, route.component, route, - getSourceSegment(rawSegment), getPathIndexShift(rawSegment) + paths.length, + getSourceSegmentGroup(rawSegment), getPathIndexShift(rawSegment) + segments.length, newInheritedResolve); return [new TreeNode(snapshot, [])]; } - const {consumedPaths, parameters, lastChild} = - match(rawSegment, route, paths, inherited.snapshot); - const rawSlicedPath = paths.slice(lastChild); + const {consumedSegments, parameters, lastChild} = + match(rawSegment, route, segments, inherited.snapshot); + const rawSlicedSegments = segments.slice(lastChild); const childConfig = getChildConfig(route); - const {segment, slicedPath} = split(rawSegment, consumedPaths, rawSlicedPath, childConfig); + const {segmentGroup, slicedSegments} = + split(rawSegment, consumedSegments, rawSlicedSegments, childConfig); const snapshot = new ActivatedRouteSnapshot( - consumedPaths, Object.freeze(merge(inherited.allParams, parameters)), + consumedSegments, Object.freeze(merge(inherited.allParams, parameters)), merge(inherited.allData, getData(route)), outlet, route.component, route, - getSourceSegment(rawSegment), getPathIndexShift(rawSegment) + consumedPaths.length, + getSourceSegmentGroup(rawSegment), getPathIndexShift(rawSegment) + consumedSegments.length, newInheritedResolve); const newInherited = route.component ? InheritedFromParent.empty(snapshot) : new InheritedFromParent(inherited, snapshot, parameters, getData(route), newInheritedResolve); - if (slicedPath.length === 0 && segment.hasChildren()) { - const children = processSegmentChildren(childConfig, segment, newInherited); + if (slicedSegments.length === 0 && segmentGroup.hasChildren()) { + const children = processChildren(childConfig, segmentGroup, newInherited); return [new TreeNode(snapshot, children)]; - } else if (childConfig.length === 0 && slicedPath.length === 0) { + } else if (childConfig.length === 0 && slicedSegments.length === 0) { return [new TreeNode(snapshot, [])]; } else { - const children = processPathsWithParams( - childConfig, segment, pathIndex + lastChild, slicedPath, newInherited, PRIMARY_OUTLET); + const children = processSegment( + childConfig, segmentGroup, pathIndex + lastChild, slicedSegments, newInherited, + PRIMARY_OUTLET); return [new TreeNode(snapshot, children)]; } } @@ -164,27 +167,28 @@ function getChildConfig(route: Route): Route[] { } function match( - segment: UrlSegment, route: Route, paths: UrlPathWithParams[], parent: ActivatedRouteSnapshot) { + segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment[], + parent: ActivatedRouteSnapshot) { if (route.path === '') { if ((route.terminal || route.pathMatch === 'full') && - (segment.hasChildren() || paths.length > 0)) { + (segmentGroup.hasChildren() || segments.length > 0)) { throw new NoMatch(); } else { const params = parent ? parent.params : {}; - return {consumedPaths: [], lastChild: 0, parameters: params}; + return {consumedSegments: [], lastChild: 0, parameters: params}; } } const path = route.path; const parts = path.split('/'); const posParameters: {[key: string]: any} = {}; - const consumedPaths: UrlPathWithParams[] = []; + const consumedSegments: UrlSegment[] = []; let currentIndex = 0; for (let i = 0; i < parts.length; ++i) { - if (currentIndex >= paths.length) throw new NoMatch(); - const current = paths[currentIndex]; + if (currentIndex >= segments.length) throw new NoMatch(); + const current = segments[currentIndex]; const p = parts[i]; const isPosParam = p.startsWith(':'); @@ -193,17 +197,17 @@ function match( if (isPosParam) { posParameters[p.substring(1)] = current.path; } - consumedPaths.push(current); + consumedSegments.push(current); currentIndex++; } if ((route.terminal || route.pathMatch === 'full') && - (segment.hasChildren() || currentIndex < paths.length)) { + (segmentGroup.hasChildren() || currentIndex < segments.length)) { throw new NoMatch(); } - const parameters = merge(posParameters, consumedPaths[consumedPaths.length - 1].parameters); - return {consumedPaths, lastChild: currentIndex, parameters}; + const parameters = merge(posParameters, consumedSegments[consumedSegments.length - 1].parameters); + return {consumedSegments, lastChild: currentIndex, parameters}; } function checkOutletNameUniqueness(nodes: TreeNode[]): void { @@ -219,62 +223,64 @@ function checkOutletNameUniqueness(nodes: TreeNode[]): v }); } -function getSourceSegment(segment: UrlSegment): UrlSegment { - let s = segment; +function getSourceSegmentGroup(segmentGroup: UrlSegmentGroup): UrlSegmentGroup { + let s = segmentGroup; while (s._sourceSegment) { s = s._sourceSegment; } return s; } -function getPathIndexShift(segment: UrlSegment): number { - let s = segment; - let res = (s._pathIndexShift ? s._pathIndexShift : 0); +function getPathIndexShift(segmentGroup: UrlSegmentGroup): number { + let s = segmentGroup; + let res = (s._segmentIndexShift ? s._segmentIndexShift : 0); while (s._sourceSegment) { s = s._sourceSegment; - res += (s._pathIndexShift ? s._pathIndexShift : 0); + res += (s._segmentIndexShift ? s._segmentIndexShift : 0); } return res - 1; } function split( - segment: UrlSegment, consumedPaths: UrlPathWithParams[], slicedPath: UrlPathWithParams[], + segmentGroup: UrlSegmentGroup, consumedSegments: UrlSegment[], slicedSegments: UrlSegment[], config: Route[]) { - if (slicedPath.length > 0 && - containsEmptyPathMatchesWithNamedOutlets(segment, slicedPath, config)) { - const s = new UrlSegment( - consumedPaths, - createChildrenForEmptyPaths( - segment, consumedPaths, config, new UrlSegment(slicedPath, segment.children))); - s._sourceSegment = segment; - s._pathIndexShift = consumedPaths.length; - return {segment: s, slicedPath: []}; + if (slicedSegments.length > 0 && + containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, config)) { + const s = new UrlSegmentGroup( + consumedSegments, createChildrenForEmptyPaths( + segmentGroup, consumedSegments, config, + new UrlSegmentGroup(slicedSegments, segmentGroup.children))); + s._sourceSegment = segmentGroup; + s._segmentIndexShift = consumedSegments.length; + return {segmentGroup: s, slicedSegments: []}; - } else if (slicedPath.length === 0 && containsEmptyPathMatches(segment, slicedPath, config)) { - const s = new UrlSegment( - segment.pathsWithParams, - addEmptyPathsToChildrenIfNeeded(segment, slicedPath, config, segment.children)); - s._sourceSegment = segment; - s._pathIndexShift = consumedPaths.length; - return {segment: s, slicedPath}; + } else if ( + slicedSegments.length === 0 && + containsEmptyPathMatches(segmentGroup, slicedSegments, config)) { + const s = new UrlSegmentGroup( + segmentGroup.segments, addEmptyPathsToChildrenIfNeeded( + segmentGroup, slicedSegments, config, segmentGroup.children)); + s._sourceSegment = segmentGroup; + s._segmentIndexShift = consumedSegments.length; + return {segmentGroup: s, slicedSegments}; } else { - const s = new UrlSegment(segment.pathsWithParams, segment.children); - s._sourceSegment = segment; - s._pathIndexShift = consumedPaths.length; - return {segment: s, slicedPath}; + const s = new UrlSegmentGroup(segmentGroup.segments, segmentGroup.children); + s._sourceSegment = segmentGroup; + s._segmentIndexShift = consumedSegments.length; + return {segmentGroup: s, slicedSegments}; } } function addEmptyPathsToChildrenIfNeeded( - segment: UrlSegment, slicedPath: UrlPathWithParams[], routes: Route[], - children: {[name: string]: UrlSegment}): {[name: string]: UrlSegment} { - const res: {[name: string]: UrlSegment} = {}; + segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], routes: Route[], + children: {[name: string]: UrlSegmentGroup}): {[name: string]: UrlSegmentGroup} { + const res: {[name: string]: UrlSegmentGroup} = {}; for (let r of routes) { - if (emptyPathMatch(segment, slicedPath, r) && !children[getOutlet(r)]) { - const s = new UrlSegment([], {}); - s._sourceSegment = segment; - s._pathIndexShift = segment.pathsWithParams.length; + if (emptyPathMatch(segmentGroup, slicedSegments, r) && !children[getOutlet(r)]) { + const s = new UrlSegmentGroup([], {}); + s._sourceSegment = segmentGroup; + s._segmentIndexShift = segmentGroup.segments.length; res[getOutlet(r)] = s; } } @@ -282,18 +288,18 @@ function addEmptyPathsToChildrenIfNeeded( } function createChildrenForEmptyPaths( - segment: UrlSegment, consumedPaths: UrlPathWithParams[], routes: Route[], - primarySegment: UrlSegment): {[name: string]: UrlSegment} { - const res: {[name: string]: UrlSegment} = {}; + segmentGroup: UrlSegmentGroup, consumedSegments: UrlSegment[], routes: Route[], + primarySegment: UrlSegmentGroup): {[name: string]: UrlSegmentGroup} { + const res: {[name: string]: UrlSegmentGroup} = {}; res[PRIMARY_OUTLET] = primarySegment; - primarySegment._sourceSegment = segment; - primarySegment._pathIndexShift = consumedPaths.length; + primarySegment._sourceSegment = segmentGroup; + primarySegment._segmentIndexShift = consumedSegments.length; for (let r of routes) { if (r.path === '' && getOutlet(r) !== PRIMARY_OUTLET) { - const s = new UrlSegment([], {}); - s._sourceSegment = segment; - s._pathIndexShift = consumedPaths.length; + const s = new UrlSegmentGroup([], {}); + s._sourceSegment = segmentGroup; + s._segmentIndexShift = consumedSegments.length; res[getOutlet(r)] = s; } } @@ -301,19 +307,23 @@ function createChildrenForEmptyPaths( } function containsEmptyPathMatchesWithNamedOutlets( - segment: UrlSegment, slicedPath: UrlPathWithParams[], routes: Route[]): boolean { + segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], routes: Route[]): boolean { return routes - .filter(r => emptyPathMatch(segment, slicedPath, r) && getOutlet(r) !== PRIMARY_OUTLET) + .filter( + r => emptyPathMatch(segmentGroup, slicedSegments, r) && + getOutlet(r) !== PRIMARY_OUTLET) .length > 0; } function containsEmptyPathMatches( - segment: UrlSegment, slicedPath: UrlPathWithParams[], routes: Route[]): boolean { - return routes.filter(r => emptyPathMatch(segment, slicedPath, r)).length > 0; + segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], routes: Route[]): boolean { + return routes.filter(r => emptyPathMatch(segmentGroup, slicedSegments, r)).length > 0; } -function emptyPathMatch(segment: UrlSegment, slicedPath: UrlPathWithParams[], r: Route): boolean { - if ((segment.hasChildren() || slicedPath.length > 0) && (r.terminal || r.pathMatch === 'full')) +function emptyPathMatch( + segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], r: Route): boolean { + if ((segmentGroup.hasChildren() || slicedSegments.length > 0) && + (r.terminal || r.pathMatch === 'full')) return false; return r.path === '' && r.redirectTo === undefined; } diff --git a/modules/@angular/router/src/router_state.ts b/modules/@angular/router/src/router_state.ts index 21d8a583dd..540a01e336 100644 --- a/modules/@angular/router/src/router_state.ts +++ b/modules/@angular/router/src/router_state.ts @@ -12,7 +12,7 @@ import {Observable} from 'rxjs/Observable'; import {Data, ResolveData, Route} from './config'; import {PRIMARY_OUTLET, Params} from './shared'; -import {UrlPathWithParams, UrlSegment, UrlTree} from './url_tree'; +import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree'; import {merge, shallowEqual, shallowEqualArrays} from './utils/collection'; import {Tree, TreeNode} from './utils/tree'; @@ -49,7 +49,7 @@ export class RouterState extends Tree { export function createEmptyState(urlTree: UrlTree, rootComponent: Type): RouterState { const snapshot = createEmptyStateSnapshot(urlTree, rootComponent); - const emptyUrl = new BehaviorSubject([new UrlPathWithParams('', {})]); + const emptyUrl = new BehaviorSubject([new UrlSegment('', {})]); const emptyParams = new BehaviorSubject({}); const emptyData = new BehaviorSubject({}); const emptyQueryParams = new BehaviorSubject({}); @@ -99,7 +99,7 @@ export class ActivatedRoute { * @internal */ constructor( - public url: Observable, public params: Observable, + public url: Observable, public params: Observable, public data: Observable, public outlet: string, public component: Type|string, futureSnapshot: ActivatedRouteSnapshot) { this._futureSnapshot = futureSnapshot; @@ -158,7 +158,7 @@ export class ActivatedRouteSnapshot { _routeConfig: Route; /** @internal **/ - _urlSegment: UrlSegment; + _urlSegment: UrlSegmentGroup; /** @internal */ _lastPathIndex: number; @@ -170,9 +170,9 @@ export class ActivatedRouteSnapshot { * @internal */ constructor( - public url: UrlPathWithParams[], public params: Params, public data: Data, - public outlet: string, public component: Type|string, routeConfig: Route, - urlSegment: UrlSegment, lastPathIndex: number, resolve: InheritedResolve) { + public url: UrlSegment[], public params: Params, public data: Data, public outlet: string, + public component: Type|string, routeConfig: Route, urlSegment: UrlSegmentGroup, + lastPathIndex: number, resolve: InheritedResolve) { this._routeConfig = routeConfig; this._urlSegment = urlSegment; this._lastPathIndex = lastPathIndex; diff --git a/modules/@angular/router/src/url_tree.ts b/modules/@angular/router/src/url_tree.ts index fef13c0412..55b950ab9a 100644 --- a/modules/@angular/router/src/url_tree.ts +++ b/modules/@angular/router/src/url_tree.ts @@ -10,53 +10,53 @@ import {PRIMARY_OUTLET} from './shared'; import {forEach, shallowEqual} from './utils/collection'; export function createEmptyUrlTree() { - return new UrlTree(new UrlSegment([], {}), {}, null); + return new UrlTree(new UrlSegmentGroup([], {}), {}, null); } export function containsTree(container: UrlTree, containee: UrlTree, exact: boolean): boolean { if (exact) { - return equalSegments(container.root, containee.root); + return equalSegmentGroups(container.root, containee.root); } else { - return containsSegment(container.root, containee.root); + return containsSegmentGroup(container.root, containee.root); } } -function equalSegments(container: UrlSegment, containee: UrlSegment): boolean { - if (!equalPath(container.pathsWithParams, containee.pathsWithParams)) return false; +function equalSegmentGroups(container: UrlSegmentGroup, containee: UrlSegmentGroup): boolean { + if (!equalPath(container.segments, containee.segments)) return false; if (container.numberOfChildren !== containee.numberOfChildren) return false; for (let c in containee.children) { if (!container.children[c]) return false; - if (!equalSegments(container.children[c], containee.children[c])) return false; + if (!equalSegmentGroups(container.children[c], containee.children[c])) return false; } return true; } -function containsSegment(container: UrlSegment, containee: UrlSegment): boolean { - return containsSegmentHelper(container, containee, containee.pathsWithParams); +function containsSegmentGroup(container: UrlSegmentGroup, containee: UrlSegmentGroup): boolean { + return containsSegmentGroupHelper(container, containee, containee.segments); } -function containsSegmentHelper( - container: UrlSegment, containee: UrlSegment, containeePaths: UrlPathWithParams[]): boolean { - if (container.pathsWithParams.length > containeePaths.length) { - const current = container.pathsWithParams.slice(0, containeePaths.length); +function containsSegmentGroupHelper( + container: UrlSegmentGroup, containee: UrlSegmentGroup, containeePaths: UrlSegment[]): boolean { + if (container.segments.length > containeePaths.length) { + const current = container.segments.slice(0, containeePaths.length); if (!equalPath(current, containeePaths)) return false; if (containee.hasChildren()) return false; return true; - } else if (container.pathsWithParams.length === containeePaths.length) { - if (!equalPath(container.pathsWithParams, containeePaths)) return false; + } else if (container.segments.length === containeePaths.length) { + if (!equalPath(container.segments, containeePaths)) return false; for (let c in containee.children) { if (!container.children[c]) return false; - if (!containsSegment(container.children[c], containee.children[c])) return false; + if (!containsSegmentGroup(container.children[c], containee.children[c])) return false; } return true; } else { - const current = containeePaths.slice(0, container.pathsWithParams.length); - const next = containeePaths.slice(container.pathsWithParams.length); - if (!equalPath(container.pathsWithParams, current)) return false; + const current = containeePaths.slice(0, container.segments.length); + const next = containeePaths.slice(container.segments.length); + if (!equalPath(container.segments, current)) return false; if (!container.children[PRIMARY_OUTLET]) return false; - return containsSegmentHelper(container.children[PRIMARY_OUTLET], containee, next); + return containsSegmentGroupHelper(container.children[PRIMARY_OUTLET], containee, next); } } @@ -70,7 +70,7 @@ export class UrlTree { * @internal */ constructor( - public root: UrlSegment, public queryParams: {[key: string]: string}, + public root: UrlSegmentGroup, public queryParams: {[key: string]: string}, public fragment: string) {} toString(): string { return new DefaultUrlSerializer().serialize(this); } @@ -79,20 +79,19 @@ export class UrlTree { /** * @stable */ -export class UrlSegment { +export class UrlSegmentGroup { /** * @internal */ - _sourceSegment: UrlSegment; + _sourceSegment: UrlSegmentGroup; /** * @internal */ - _pathIndexShift: number; + _segmentIndexShift: number; - public parent: UrlSegment = null; - constructor( - public pathsWithParams: UrlPathWithParams[], public children: {[key: string]: UrlSegment}) { + public parent: UrlSegmentGroup = null; + constructor(public segments: UrlSegment[], public children: {[key: string]: UrlSegmentGroup}) { forEach(children, (v: any, k: any) => v.parent = this); } @@ -113,12 +112,12 @@ export class UrlSegment { /** * @stable */ -export class UrlPathWithParams { +export class UrlSegment { constructor(public path: string, public parameters: {[key: string]: string}) {} toString(): string { return serializePath(this); } } -export function equalPathsWithParams(a: UrlPathWithParams[], b: UrlPathWithParams[]): boolean { +export function equalSegments(a: UrlSegment[], b: UrlSegment[]): boolean { if (a.length !== b.length) return false; for (let i = 0; i < a.length; ++i) { if (a[i].path !== b[i].path) return false; @@ -127,7 +126,7 @@ export function equalPathsWithParams(a: UrlPathWithParams[], b: UrlPathWithParam return true; } -export function equalPath(a: UrlPathWithParams[], b: UrlPathWithParams[]): boolean { +export function equalPath(a: UrlSegment[], b: UrlSegment[]): boolean { if (a.length !== b.length) return false; for (let i = 0; i < a.length; ++i) { if (a[i].path !== b[i].path) return false; @@ -136,14 +135,14 @@ export function equalPath(a: UrlPathWithParams[], b: UrlPathWithParams[]): boole } export function mapChildrenIntoArray( - segment: UrlSegment, fn: (v: UrlSegment, k: string) => T[]): T[] { + segment: UrlSegmentGroup, fn: (v: UrlSegmentGroup, k: string) => T[]): T[] { let res: T[] = []; - forEach(segment.children, (child: UrlSegment, childOutlet: string) => { + forEach(segment.children, (child: UrlSegmentGroup, childOutlet: string) => { if (childOutlet === PRIMARY_OUTLET) { res = res.concat(fn(child, childOutlet)); } }); - forEach(segment.children, (child: UrlSegment, childOutlet: string) => { + forEach(segment.children, (child: UrlSegmentGroup, childOutlet: string) => { if (childOutlet !== PRIMARY_OUTLET) { res = res.concat(fn(child, childOutlet)); } @@ -190,17 +189,17 @@ export class DefaultUrlSerializer implements UrlSerializer { } } -export function serializePaths(segment: UrlSegment): string { - return segment.pathsWithParams.map(p => serializePath(p)).join('/'); +export function serializePaths(segment: UrlSegmentGroup): string { + return segment.segments.map(p => serializePath(p)).join('/'); } -function serializeSegment(segment: UrlSegment, root: boolean): string { +function serializeSegment(segment: UrlSegmentGroup, root: boolean): string { if (segment.hasChildren() && root) { const primary = segment.children[PRIMARY_OUTLET] ? serializeSegment(segment.children[PRIMARY_OUTLET], false) : ''; const children: string[] = []; - forEach(segment.children, (v: UrlSegment, k: string) => { + forEach(segment.children, (v: UrlSegmentGroup, k: string) => { if (k !== PRIMARY_OUTLET) { children.push(`${k}:${serializeSegment(v, false)}`); } @@ -212,7 +211,7 @@ function serializeSegment(segment: UrlSegment, root: boolean): string { } } else if (segment.hasChildren() && !root) { - const children = mapChildrenIntoArray(segment, (v: UrlSegment, k: string) => { + const children = mapChildrenIntoArray(segment, (v: UrlSegmentGroup, k: string) => { if (k === PRIMARY_OUTLET) { return [serializeSegment(segment.children[PRIMARY_OUTLET], false)]; } else { @@ -226,7 +225,7 @@ function serializeSegment(segment: UrlSegment, root: boolean): string { } } -export function serializePath(path: UrlPathWithParams): string { +export function serializePath(path: UrlSegment): string { return `${encodeURIComponent(path.path)}${serializeParams(path.parameters)}`; } @@ -256,7 +255,7 @@ function pairs(obj: {[key: string]: T}): Pair[] { } const SEGMENT_RE = /^[^\/\(\)\?;=&#]+/; -function matchPathWithParams(str: string): string { +function matchSegments(str: string): string { SEGMENT_RE.lastIndex = 0; const match = SEGMENT_RE.exec(str); return match ? match[0] : ''; @@ -289,19 +288,19 @@ class UrlParser { this.remaining = this.remaining.substring(str.length); } - parseRootSegment(): UrlSegment { + parseRootSegment(): UrlSegmentGroup { if (this.remaining.startsWith('/')) { this.capture('/'); } if (this.remaining === '' || this.remaining.startsWith('?') || this.remaining.startsWith('#')) { - return new UrlSegment([], {}); + return new UrlSegmentGroup([], {}); } else { - return new UrlSegment([], this.parseSegmentChildren()); + return new UrlSegmentGroup([], this.parseChildren()); } } - parseSegmentChildren(): {[key: string]: UrlSegment} { + parseChildren(): {[key: string]: UrlSegmentGroup} { if (this.remaining.length == 0) { return {}; } @@ -312,34 +311,34 @@ class UrlParser { let paths: any[] = []; if (!this.peekStartsWith('(')) { - paths.push(this.parsePathWithParams()); + paths.push(this.parseSegments()); } while (this.peekStartsWith('/') && !this.peekStartsWith('//') && !this.peekStartsWith('/(')) { this.capture('/'); - paths.push(this.parsePathWithParams()); + paths.push(this.parseSegments()); } - let children: {[key: string]: UrlSegment} = {}; + let children: {[key: string]: UrlSegmentGroup} = {}; if (this.peekStartsWith('/(')) { this.capture('/'); children = this.parseParens(true); } - let res: {[key: string]: UrlSegment} = {}; + let res: {[key: string]: UrlSegmentGroup} = {}; if (this.peekStartsWith('(')) { res = this.parseParens(false); } if (paths.length > 0 || Object.keys(children).length > 0) { - res[PRIMARY_OUTLET] = new UrlSegment(paths, children); + res[PRIMARY_OUTLET] = new UrlSegmentGroup(paths, children); } return res; } - parsePathWithParams(): UrlPathWithParams { - const path = matchPathWithParams(this.remaining); + parseSegments(): UrlSegment { + const path = matchSegments(this.remaining); if (path === '' && this.peekStartsWith(';')) { throw new Error(`Empty path url segment cannot have parameters: '${this.remaining}'.`); } @@ -349,7 +348,7 @@ class UrlParser { if (this.peekStartsWith(';')) { matrixParams = this.parseMatrixParams(); } - return new UrlPathWithParams(decodeURIComponent(path), matrixParams); + return new UrlSegment(decodeURIComponent(path), matrixParams); } parseQueryParams(): {[key: string]: any} { @@ -383,7 +382,7 @@ class UrlParser { } parseParam(params: {[key: string]: any}): void { - const key = matchPathWithParams(this.remaining); + const key = matchSegments(this.remaining); if (!key) { return; } @@ -391,7 +390,7 @@ class UrlParser { let value: any = 'true'; if (this.peekStartsWith('=')) { this.capture('='); - const valueMatch = matchPathWithParams(this.remaining); + const valueMatch = matchSegments(this.remaining); if (valueMatch) { value = valueMatch; this.capture(value); @@ -419,11 +418,11 @@ class UrlParser { params[decodeURIComponent(key)] = decodeURIComponent(value); } - parseParens(allowPrimary: boolean): {[key: string]: UrlSegment} { - const segments: {[key: string]: UrlSegment} = {}; + parseParens(allowPrimary: boolean): {[key: string]: UrlSegmentGroup} { + const segments: {[key: string]: UrlSegmentGroup} = {}; this.capture('('); while (!this.peekStartsWith(')') && this.remaining.length > 0) { - const path = matchPathWithParams(this.remaining); + const path = matchSegments(this.remaining); const next = this.remaining[path.length]; @@ -442,9 +441,9 @@ class UrlParser { outletName = PRIMARY_OUTLET; } - const children = this.parseSegmentChildren(); + const children = this.parseChildren(); segments[outletName] = Object.keys(children).length === 1 ? children[PRIMARY_OUTLET] : - new UrlSegment([], children); + new UrlSegmentGroup([], children); if (this.peekStartsWith('//')) { this.capture('//'); } diff --git a/modules/@angular/router/test/apply_redirects.spec.ts b/modules/@angular/router/test/apply_redirects.spec.ts index d4064c8e4f..c9f635bf88 100644 --- a/modules/@angular/router/test/apply_redirects.spec.ts +++ b/modules/@angular/router/test/apply_redirects.spec.ts @@ -12,7 +12,7 @@ import {of } from 'rxjs/observable/of'; import {applyRedirects} from '../src/apply_redirects'; import {Routes} from '../src/config'; import {LoadedRouterConfig} from '../src/router_config_loader'; -import {DefaultUrlSerializer, UrlSegment, UrlTree, equalPathsWithParams} from '../src/url_tree'; +import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree, equalSegments} from '../src/url_tree'; describe('applyRedirects', () => { @@ -367,10 +367,9 @@ function compareTrees(actual: UrlTree, expected: UrlTree): void { compareSegments(actual.root, expected.root, error); } -function compareSegments(actual: UrlSegment, expected: UrlSegment, error: string): void { +function compareSegments(actual: UrlSegmentGroup, expected: UrlSegmentGroup, error: string): void { expect(actual).toBeDefined(error); - expect(equalPathsWithParams(actual.pathsWithParams, expected.pathsWithParams)) - .toEqual(true, error); + expect(equalSegments(actual.segments, expected.segments)).toEqual(true, error); expect(Object.keys(actual.children).length).toEqual(Object.keys(expected.children).length, error); diff --git a/modules/@angular/router/test/create_router_state.spec.ts b/modules/@angular/router/test/create_router_state.spec.ts index e1f18ddf88..8b1a448801 100644 --- a/modules/@angular/router/test/create_router_state.spec.ts +++ b/modules/@angular/router/test/create_router_state.spec.ts @@ -11,12 +11,12 @@ import {createRouterState} from '../src/create_router_state'; import {recognize} from '../src/recognize'; import {ActivatedRoute, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from '../src/router_state'; import {PRIMARY_OUTLET, Params} from '../src/shared'; -import {DefaultUrlSerializer, UrlSegment, UrlTree} from '../src/url_tree'; +import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree} from '../src/url_tree'; import {TreeNode} from '../src/utils/tree'; describe('create router state', () => { const emptyState = () => - createEmptyState(new UrlTree(new UrlSegment([], {}), {}, null), RootComponent); + createEmptyState(new UrlTree(new UrlSegmentGroup([], {}), {}, null), RootComponent); it('should work create new state', () => { const state = createRouterState( diff --git a/modules/@angular/router/test/create_url_tree.spec.ts b/modules/@angular/router/test/create_url_tree.spec.ts index 094b5479ed..3e2a753960 100644 --- a/modules/@angular/router/test/create_url_tree.spec.ts +++ b/modules/@angular/router/test/create_url_tree.spec.ts @@ -11,7 +11,7 @@ import {BehaviorSubject} from 'rxjs/BehaviorSubject'; import {createUrlTree} from '../src/create_url_tree'; import {ActivatedRoute, ActivatedRouteSnapshot, advanceActivatedRoute} from '../src/router_state'; import {PRIMARY_OUTLET, Params} from '../src/shared'; -import {DefaultUrlSerializer, UrlPathWithParams, UrlSegment, UrlTree} from '../src/url_tree'; +import {DefaultUrlSerializer, UrlSegment, UrlSegmentGroup, UrlTree} from '../src/url_tree'; describe('createUrlTree', () => { const serializer = new DefaultUrlSerializer(); @@ -37,7 +37,7 @@ describe('createUrlTree', () => { it('should stringify positional parameters', () => { const p = serializer.parse('/a/b'); const t = createRoot(p, ['/one', 11]); - const params = t.root.children[PRIMARY_OUTLET].pathsWithParams; + const params = t.root.children[PRIMARY_OUTLET].segments; expect(params[0].path).toEqual('one'); expect(params[1].path).toEqual('11'); }); @@ -209,8 +209,8 @@ function createRoot(tree: UrlTree, commands: any[], queryParams?: Params, fragme } function create( - segment: UrlSegment, startIndex: number, tree: UrlTree, commands: any[], queryParams?: Params, - fragment?: string) { + segment: UrlSegmentGroup, startIndex: number, tree: UrlTree, commands: any[], + queryParams?: Params, fragment?: string) { if (!segment) { expect(segment).toBeDefined(); } diff --git a/modules/@angular/router/test/resolve.spec.ts b/modules/@angular/router/test/resolve.spec.ts index 458e3df896..1f3ac16855 100644 --- a/modules/@angular/router/test/resolve.spec.ts +++ b/modules/@angular/router/test/resolve.spec.ts @@ -10,7 +10,7 @@ import {Routes} from '../src/config'; import {recognize} from '../src/recognize'; import {resolve} from '../src/resolve'; import {RouterStateSnapshot} from '../src/router_state'; -import {DefaultUrlSerializer, UrlSegment, UrlTree} from '../src/url_tree'; +import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree} from '../src/url_tree'; describe('resolve', () => { it('should resolve components', () => { diff --git a/modules/@angular/router/test/url_serializer.spec.ts b/modules/@angular/router/test/url_serializer.spec.ts index 2204c3e7d9..a8081311ae 100644 --- a/modules/@angular/router/test/url_serializer.spec.ts +++ b/modules/@angular/router/test/url_serializer.spec.ts @@ -7,7 +7,7 @@ */ import {PRIMARY_OUTLET} from '../src/shared'; -import {DefaultUrlSerializer, UrlSegment, serializePath} from '../src/url_tree'; +import {DefaultUrlSerializer, UrlSegmentGroup, serializePath} from '../src/url_tree'; describe('url serializer', () => { const url = new DefaultUrlSerializer(); @@ -174,8 +174,8 @@ describe('url serializer', () => { `/${encodeURIComponent("one two")};${encodeURIComponent("p 1")}=${encodeURIComponent("v 1")};${encodeURIComponent("p 2")}=${encodeURIComponent("v 2")}`; const tree = url.parse(u); - expect(tree.root.children[PRIMARY_OUTLET].pathsWithParams[0].path).toEqual('one two'); - expect(tree.root.children[PRIMARY_OUTLET].pathsWithParams[0].parameters) + expect(tree.root.children[PRIMARY_OUTLET].segments[0].path).toEqual('one two'); + expect(tree.root.children[PRIMARY_OUTLET].segments[0].parameters) .toEqual({['p 1']: 'v 1', ['p 2']: 'v 2'}); expect(url.serialize(tree)).toEqual(u); }); @@ -210,11 +210,12 @@ describe('url serializer', () => { }); }); -function expectSegment(segment: UrlSegment, expected: string, hasChildren: boolean = false): void { - if (segment.pathsWithParams.filter(s => s.path === '').length > 0) { - throw new Error(`UrlPathWithParams cannot be empty ${segment.pathsWithParams}`); +function expectSegment( + segment: UrlSegmentGroup, expected: string, hasChildren: boolean = false): void { + if (segment.segments.filter(s => s.path === '').length > 0) { + throw new Error(`UrlSegments cannot be empty ${segment.segments}`); } - const p = segment.pathsWithParams.map(p => serializePath(p)).join('/'); + const p = segment.segments.map(p => serializePath(p)).join('/'); expect(p).toEqual(expected); expect(Object.keys(segment.children).length > 0).toEqual(hasChildren); } diff --git a/tools/public_api_guard/router/index.d.ts b/tools/public_api_guard/router/index.d.ts index 67e09d6215..1956a58005 100644 --- a/tools/public_api_guard/router/index.d.ts +++ b/tools/public_api_guard/router/index.d.ts @@ -5,7 +5,7 @@ export declare class ActivatedRoute { outlet: string; params: Observable; snapshot: ActivatedRouteSnapshot; - url: Observable; + url: Observable; toString(): string; } @@ -15,7 +15,7 @@ export declare class ActivatedRouteSnapshot { data: Data; outlet: string; params: Params; - url: UrlPathWithParams[]; + url: UrlSegment[]; toString(): string; } @@ -251,7 +251,7 @@ export declare class RoutesRecognized { } /** @stable */ -export declare class UrlPathWithParams { +export declare class UrlSegment { parameters: { [key: string]: string; }; @@ -274,6 +274,6 @@ export declare class UrlTree { queryParams: { [key: string]: string; }; - root: UrlSegment; + root: UrlSegmentGroup; toString(): string; }