/** * @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 (typeof ngDevMode === 'undefined' || ngDevMode) { 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 { 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()); } /** Returns the `route.outlet` or PRIMARY_OUTLET if none exists. */ export function getOutlet(route: Route): string { return route.outlet || PRIMARY_OUTLET; }