134 lines
5.0 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {EmptyOutletComponent} from '../components/empty_outlet';
import {Route, Routes} from '../config';
import {PRIMARY_OUTLET} from '../shared';
export function validateConfig(config: Routes, parentPath: string = ''): void {
// forEach doesn't iterate undefined values
for (let i = 0; i < config.length; i++) {
const route: Route = config[i];
const fullPath: string = getFullPath(parentPath, route);
validateNode(route, fullPath);
}
}
function validateNode(route: Route, fullPath: string): void {
if (!route) {
throw new Error(`
Invalid configuration of route '${fullPath}': Encountered undefined route.
The reason might be an extra comma.
Example:
const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },, << two commas
{ path: 'detail/:id', component: HeroDetailComponent }
];
`);
}
if (Array.isArray(route)) {
throw new Error(`Invalid configuration of route '${fullPath}': Array cannot be specified`);
}
if (!route.component && !route.children && !route.loadChildren &&
(route.outlet && route.outlet !== PRIMARY_OUTLET)) {
throw new Error(`Invalid configuration of route '${
fullPath}': a componentless route without children or loadChildren cannot have a named outlet set`);
}
if (route.redirectTo && route.children) {
throw new Error(`Invalid configuration of route '${
fullPath}': redirectTo and children cannot be used together`);
}
if (route.redirectTo && route.loadChildren) {
throw new Error(`Invalid configuration of route '${
fullPath}': redirectTo and loadChildren cannot be used together`);
}
if (route.children && route.loadChildren) {
throw new Error(`Invalid configuration of route '${
fullPath}': children and loadChildren cannot be used together`);
}
if (route.redirectTo && route.component) {
throw new Error(`Invalid configuration of route '${
fullPath}': redirectTo and component cannot be used together`);
}
if (route.path && route.matcher) {
throw new Error(
`Invalid configuration of route '${fullPath}': path and matcher cannot be used together`);
}
if (route.redirectTo === void 0 && !route.component && !route.children && !route.loadChildren) {
throw new Error(`Invalid configuration of route '${
fullPath}'. One of the following must be provided: component, redirectTo, children or loadChildren`);
}
if (route.path === void 0 && route.matcher === void 0) {
throw new Error(`Invalid configuration of route '${
fullPath}': routes must have either a path or a matcher specified`);
}
if (typeof route.path === 'string' && route.path.charAt(0) === '/') {
throw new Error(`Invalid configuration of route '${fullPath}': path cannot start with a slash`);
}
if (route.path === '' && route.redirectTo !== void 0 && route.pathMatch === void 0) {
const exp =
`The default value of 'pathMatch' is 'prefix', but often the intent is to use 'full'.`;
throw new Error(`Invalid configuration of route '{path: "${fullPath}", redirectTo: "${
route.redirectTo}"}': please provide 'pathMatch'. ${exp}`);
}
if (route.pathMatch !== void 0 && route.pathMatch !== 'full' && route.pathMatch !== 'prefix') {
throw new Error(`Invalid configuration of route '${
fullPath}': pathMatch can only be set to 'prefix' or 'full'`);
}
if (route.children) {
validateConfig(route.children, fullPath);
}
}
function getFullPath(parentPath: string, currentRoute: Route): string {
if (!currentRoute) {
return parentPath;
}
if (!parentPath && !currentRoute.path) {
return '';
} else if (parentPath && !currentRoute.path) {
return `${parentPath}/`;
} else if (!parentPath && currentRoute.path) {
return currentRoute.path;
} else {
return `${parentPath}/${currentRoute.path}`;
}
}
/**
* Makes a copy of the config and adds any default required properties.
*/
export function standardizeConfig(r: Route): Route {
const children = r.children && r.children.map(standardizeConfig);
const c = children ? {...r, children} : {...r};
if (!c.component && (children || c.loadChildren) && (c.outlet && c.outlet !== PRIMARY_OUTLET)) {
c.component = EmptyOutletComponent;
}
return c;
}
/** Returns of `Map` of outlet names to the `Route`s for that outlet. */
export function groupRoutesByOutlet(routes: Route[]): Map<string, Route[]> {
return routes.reduce((map, route) => {
const routeOutlet = getOutlet(route);
if (map.has(routeOutlet)) {
map.get(routeOutlet)!.push(route);
} else {
map.set(routeOutlet, [route]);
}
return map;
}, new Map<string, Route[]>());
}
/** Returns the `route.outlet` or PRIMARY_OUTLET if none exists. */
export function getOutlet(route: Route): string {
return route.outlet || PRIMARY_OUTLET;
}