2020-07-24 15:41:44 -07:00
|
|
|
/**
|
|
|
|
* @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 {
|
2020-09-02 08:01:39 -04:00
|
|
|
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
|
|
if (!route) {
|
|
|
|
throw new Error(`
|
2020-07-24 15:41:44 -07:00
|
|
|
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 }
|
|
|
|
];
|
|
|
|
`);
|
2020-09-02 08:01:39 -04:00
|
|
|
}
|
|
|
|
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'`);
|
|
|
|
}
|
2020-07-24 15:41:44 -07:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
2020-08-06 11:48:37 -07:00
|
|
|
|
|
|
|
/** 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;
|
|
|
|
}
|