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