feat(router): add pathMatch property to replace terminal
This commit is contained in:
parent
dc64e90ab9
commit
fcfddbf79c
|
@ -149,7 +149,8 @@ function match(segment: UrlSegment, route: Route, paths: UrlPathWithParams[]): {
|
|||
positionalParamSegments: {[k: string]: UrlPathWithParams}
|
||||
} {
|
||||
if (route.path === '') {
|
||||
if (route.terminal && (segment.hasChildren() || paths.length > 0)) {
|
||||
if ((route.terminal || route.pathMatch === 'full') &&
|
||||
(segment.hasChildren() || paths.length > 0)) {
|
||||
throw new NoMatch();
|
||||
} else {
|
||||
return {consumedPaths: [], lastChild: 0, positionalParamSegments: {}};
|
||||
|
@ -286,7 +287,8 @@ function containsEmptyPathRedirects(
|
|||
|
||||
function emptyPathRedirect(
|
||||
segment: UrlSegment, slicedPath: UrlPathWithParams[], r: Route): boolean {
|
||||
if ((segment.hasChildren() || slicedPath.length > 0) && r.terminal) return false;
|
||||
if ((segment.hasChildren() || slicedPath.length > 0) && (r.terminal || r.pathMatch === 'full'))
|
||||
return false;
|
||||
return r.path === '' && r.redirectTo !== undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,12 @@ export type ResolveData = {
|
|||
|
||||
export interface Route {
|
||||
path?: string;
|
||||
|
||||
/**
|
||||
* @deprecated - use `pathMatch` instead
|
||||
*/
|
||||
terminal?: boolean;
|
||||
pathMatch?: 'full'|'prefix';
|
||||
component?: Type|string;
|
||||
outlet?: string;
|
||||
canActivate?: any[];
|
||||
|
@ -53,4 +58,11 @@ function validateNode(route: Route): void {
|
|||
throw new Error(
|
||||
`Invalid route configuration of route '${route.path}': path cannot start with a slash`);
|
||||
}
|
||||
if (route.path === '' && route.redirectTo !== undefined &&
|
||||
(route.terminal === undefined && route.pathMatch === undefined)) {
|
||||
const exp =
|
||||
`The default value of 'pathMatch' is 'prefix', but often the intent is to use 'full'.`;
|
||||
throw new Error(
|
||||
`Invalid route configuration of route '{path: "${route.path}", redirectTo: "${route.redirectTo}"}': please provide 'pathMatch'. ${exp}`);
|
||||
}
|
||||
}
|
|
@ -53,7 +53,7 @@ export class RouterOutlet {
|
|||
const snapshot = activatedRoute._futureSnapshot;
|
||||
const component: any = <any>snapshot._routeConfig.component;
|
||||
|
||||
let factory;
|
||||
let factory: ComponentFactory<any>;
|
||||
try {
|
||||
factory = typeof component === 'string' ?
|
||||
snapshot._resolvedComponentFactory :
|
||||
|
|
|
@ -151,7 +151,8 @@ function processPathsWithParamsAgainstRoute(
|
|||
|
||||
function match(segment: UrlSegment, route: Route, paths: UrlPathWithParams[]) {
|
||||
if (route.path === '') {
|
||||
if (route.terminal && (segment.hasChildren() || paths.length > 0)) {
|
||||
if ((route.terminal || route.pathMatch === 'full') &&
|
||||
(segment.hasChildren() || paths.length > 0)) {
|
||||
throw new NoMatch();
|
||||
} else {
|
||||
return {consumedPaths: [], lastChild: 0, parameters: {}};
|
||||
|
@ -180,7 +181,8 @@ function match(segment: UrlSegment, route: Route, paths: UrlPathWithParams[]) {
|
|||
currentIndex++;
|
||||
}
|
||||
|
||||
if (route.terminal && (segment.hasChildren() || currentIndex < paths.length)) {
|
||||
if ((route.terminal || route.pathMatch === 'full') &&
|
||||
(segment.hasChildren() || currentIndex < paths.length)) {
|
||||
throw new NoMatch();
|
||||
}
|
||||
|
||||
|
@ -292,7 +294,8 @@ function containsEmptyPathMatches(
|
|||
}
|
||||
|
||||
function emptyPathMatch(segment: UrlSegment, slicedPath: UrlPathWithParams[], r: Route): boolean {
|
||||
if ((segment.hasChildren() || slicedPath.length > 0) && r.terminal) return false;
|
||||
if ((segment.hasChildren() || slicedPath.length > 0) && (r.terminal || r.pathMatch === 'full'))
|
||||
return false;
|
||||
return r.path === '' && r.redirectTo === undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ describe('applyRedirects', () => {
|
|||
});
|
||||
|
||||
it('should redirect empty path route only when terminal', () => {
|
||||
const config = [
|
||||
const config: RouterConfig = [
|
||||
{
|
||||
path: 'a',
|
||||
component: ComponentA,
|
||||
|
@ -168,7 +168,7 @@ describe('applyRedirects', () => {
|
|||
{path: 'b', component: ComponentB},
|
||||
]
|
||||
},
|
||||
{path: '', redirectTo: 'a', terminal: true}
|
||||
{path: '', redirectTo: 'a', pathMatch: 'full'}
|
||||
];
|
||||
|
||||
applyRedirects(tree('b'), config)
|
||||
|
@ -220,7 +220,7 @@ describe('applyRedirects', () => {
|
|||
children: [
|
||||
{path: 'b', component: ComponentB},
|
||||
{path: 'c', component: ComponentC, outlet: 'aux'},
|
||||
{path: '', terminal: true, redirectTo: 'c', outlet: 'aux'}
|
||||
{path: '', pathMatch: 'full', redirectTo: 'c', outlet: 'aux'}
|
||||
]
|
||||
}],
|
||||
'a/b', (t: UrlTree) => { compareTrees(t, tree('a/b')); });
|
||||
|
@ -287,7 +287,7 @@ describe('applyRedirects', () => {
|
|||
});
|
||||
|
||||
it('should not create a new child (terminal)', () => {
|
||||
const config = [{
|
||||
const config: RouterConfig = [{
|
||||
path: 'a',
|
||||
children: [
|
||||
{path: 'b', component: ComponentB, children: [{path: 'd', component: ComponentB}]},
|
||||
|
@ -297,7 +297,7 @@ describe('applyRedirects', () => {
|
|||
outlet: 'aux',
|
||||
children: [{path: 'e', component: ComponentC}]
|
||||
},
|
||||
{path: '', terminal: true, redirectTo: 'c', outlet: 'aux'}
|
||||
{path: '', pathMatch: 'full', redirectTo: 'c', outlet: 'aux'}
|
||||
]
|
||||
}];
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import {validateConfig} from '../src/config';
|
|||
describe('config', () => {
|
||||
describe('validateConfig', () => {
|
||||
it('should not throw when no errors', () => {
|
||||
validateConfig([{path: '', redirectTo: 'b'}, {path: 'b', component: ComponentA}]);
|
||||
validateConfig([{path: 'a', redirectTo: 'b'}, {path: 'b', component: ComponentA}]);
|
||||
});
|
||||
|
||||
it('should throw when redirectTo and children are used together', () => {
|
||||
|
@ -35,9 +35,16 @@ describe('config', () => {
|
|||
|
||||
it('should throw when path starts with a slash', () => {
|
||||
expect(() => {
|
||||
validateConfig([<any>{path: '/a', componenta: '', redirectTo: 'b'}]);
|
||||
validateConfig([<any>{path: '/a', redirectTo: 'b'}]);
|
||||
}).toThrowError(`Invalid route configuration of route '/a': path cannot start with a slash`);
|
||||
});
|
||||
|
||||
it('should throw when emptyPath is used with redirectTo without explicitly providing matching',
|
||||
() => {
|
||||
expect(() => {
|
||||
validateConfig([<any>{path: '', redirectTo: 'b'}]);
|
||||
}).toThrowError(/Invalid route configuration of route '{path: "", redirectTo: "b"}'/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -215,7 +215,8 @@ describe('recognize', () => {
|
|||
|
||||
it('should match when terminal', () => {
|
||||
checkRecognize(
|
||||
[{path: '', terminal: true, component: ComponentA}], '', (s: RouterStateSnapshot) => {
|
||||
[{path: '', pathMatch: 'full', component: ComponentA}], '',
|
||||
(s: RouterStateSnapshot) => {
|
||||
checkActivatedRoute(s.firstChild(s.root), '', {}, ComponentA);
|
||||
});
|
||||
});
|
||||
|
@ -224,7 +225,7 @@ describe('recognize', () => {
|
|||
recognize(
|
||||
RootComponent, [{
|
||||
path: '',
|
||||
terminal: true,
|
||||
pathMatch: 'full',
|
||||
component: ComponentA,
|
||||
children: [{path: 'b', component: ComponentB}]
|
||||
}],
|
||||
|
@ -290,7 +291,7 @@ describe('recognize', () => {
|
|||
component: ComponentA,
|
||||
children: [
|
||||
{path: 'b', component: ComponentB},
|
||||
{path: '', terminal: true, component: ComponentC, outlet: 'aux'}
|
||||
{path: '', pathMatch: 'full', component: ComponentC, outlet: 'aux'}
|
||||
]
|
||||
}],
|
||||
'a/b', (s: RouterStateSnapshot) => {
|
||||
|
@ -359,8 +360,8 @@ describe('recognize', () => {
|
|||
path: 'a',
|
||||
component: ComponentA,
|
||||
children: [
|
||||
{path: '', terminal: true, component: ComponentB},
|
||||
{path: '', terminal: true, component: ComponentC, outlet: 'aux'},
|
||||
{path: '', pathMatch: 'full', component: ComponentB},
|
||||
{path: '', pathMatch: 'full', component: ComponentC, outlet: 'aux'},
|
||||
]
|
||||
}],
|
||||
'a', (s: RouterStateSnapshot) => {
|
||||
|
|
|
@ -37,17 +37,17 @@ describe('Integration', () => {
|
|||
];
|
||||
});
|
||||
|
||||
fit('should navigate with a provided config',
|
||||
fakeAsync(inject(
|
||||
[Router, TestComponentBuilder, Location],
|
||||
(router: Router, tcb: TestComponentBuilder, location: Location) => {
|
||||
const fixture = createRoot(tcb, router, RootCmp);
|
||||
it('should navigate with a provided config',
|
||||
fakeAsync(inject(
|
||||
[Router, TestComponentBuilder, Location],
|
||||
(router: Router, tcb: TestComponentBuilder, location: Location) => {
|
||||
const fixture = createRoot(tcb, router, RootCmp);
|
||||
|
||||
router.navigateByUrl('/simple');
|
||||
advance(fixture);
|
||||
router.navigateByUrl('/simple');
|
||||
advance(fixture);
|
||||
|
||||
expect(location.path()).toEqual('/simple');
|
||||
})));
|
||||
expect(location.path()).toEqual('/simple');
|
||||
})));
|
||||
|
||||
|
||||
it('should update location when navigating',
|
||||
|
@ -262,7 +262,7 @@ describe('Integration', () => {
|
|||
const fixture = createRoot(tcb, router, RootCmp);
|
||||
|
||||
router.resetConfig([
|
||||
{path: '', terminal: true, component: SimpleCmp},
|
||||
{path: '', pathMatch: 'full', component: SimpleCmp},
|
||||
{path: 'user/:name', component: UserCmp}
|
||||
]);
|
||||
|
||||
|
@ -830,7 +830,7 @@ describe('Integration', () => {
|
|||
path: 'team/:id',
|
||||
component: TeamCmp,
|
||||
children: [
|
||||
{path: '', terminal: true, component: SimpleCmp}, {
|
||||
{path: '', pathMatch: 'full', component: SimpleCmp}, {
|
||||
path: 'user/:name',
|
||||
component: UserCmp,
|
||||
canDeactivate: ['CanDeactivateUser']
|
||||
|
|
|
@ -87,17 +87,9 @@ export declare type ResolveData = {
|
|||
};
|
||||
|
||||
export interface Route {
|
||||
canActivate?: any[];
|
||||
canDeactivate?: any[];
|
||||
children?: Route[];
|
||||
component?: Type | string;
|
||||
data?: Data;
|
||||
outlet?: string;
|
||||
path?: string;
|
||||
redirectTo?: string;
|
||||
resolve?: ResolveData;
|
||||
terminal?: boolean;
|
||||
}
|
||||
pathMatch?:
|
||||
/** @deprecated */ terminal?: boolean;
|
||||
|
||||
export declare class Router {
|
||||
events: Observable<Event>;
|
||||
|
|
Loading…
Reference in New Issue