Angular7如何获取所有嵌套子路由?现有实现不支持多层路由
优化Angular多层嵌套路由的菜单生成逻辑
我懂你现在的困扰——基于Adrian Paul的方案改完后,单层子路由能正常生成菜单,可多层嵌套就失效了,而且当前的实现不仅代码冗余,处理深层路由时效率也很低。咱们来一步步优化这个逻辑。
现有代码的核心问题
- 依赖Angular内部私有属性:直接用
_loadedConfig这个未公开的API,未来Angular版本更新可能会导致代码失效。 - 递归逻辑混乱:子路由和懒加载路由的处理分散在多个方法里,多层嵌套时容易遗漏层级。
- 路径拼接不健壮:简单的字符串拼接容易产生
//这类无效路径,或者层级错误。 - 重复遍历过滤:多处重复执行
routerConfig.routes.filter(x => x.data && x.data.label),增加不必要的性能开销。
优化后的实现方案
下面是一个更优雅、支持任意层级嵌套的菜单生成服务,同时解决了上述问题:
1. 菜单服务核心代码
import { Injectable } from '@angular/core'; import { Router, Route } from '@angular/router'; // 复用你的菜单接口 interface IMenuItem { id: number; label: string; routerLink: string; children?: IMenuItem[]; } @Injectable({ providedIn: 'root' }) export class MenuService { constructor(private router: Router) {} // 生成完整菜单的入口方法 generateMenu(): IMenuItem[] { const menus: IMenuItem[] = []; this.traverseRouteTree(this.router.config, '', menus, 0); return menus; } /** * 递归遍历路由树,生成菜单项 * @param routes 当前层级的路由配置 * @param parentPath 父路由的完整路径 * @param menuList 当前层级的菜单数组 * @param idCounter 菜单项ID计数器(避免重复) */ private traverseRouteTree( routes: Route[], parentPath: string, menuList: IMenuItem[], idCounter: number ): number { for (const route of routes) { // 跳过没有label的路由(不需要显示在菜单中) if (!route.data?.label) { // 递归处理当前路由的子路由 if (route.children) { idCounter = this.traverseRouteTree( route.children, this.buildFullPath(parentPath, route.path), menuList, idCounter ); } // 处理已加载的懒加载路由 if (route.loadChildren && (route as any)._loadedConfig) { idCounter = this.traverseRouteTree( (route as any)._loadedConfig.routes, this.buildFullPath(parentPath, route.path), menuList, idCounter ); } continue; } // 创建当前菜单项 const currentMenu: IMenuItem = { id: idCounter++, label: route.data.label, routerLink: this.buildFullPath('/', parentPath, route.path), children: [] }; // 递归生成子菜单 if (route.children) { idCounter = this.traverseRouteTree( route.children, this.buildFullPath(parentPath, route.path), currentMenu.children!, idCounter ); } // 处理懒加载路由的子配置 if (route.loadChildren && (route as any)._loadedConfig) { idCounter = this.traverseRouteTree( (route as any)._loadedConfig.routes, this.buildFullPath(parentPath, route.path), currentMenu.children!, idCounter ); } // 没有子菜单时移除children属性(可选,让结构更干净) if (currentMenu.children!.length === 0) { delete currentMenu.children; } menuList.push(currentMenu); } return idCounter; } /** * 构建完整路由路径,自动处理斜杠和空路径 */ private buildFullPath(...parts: string[]): string { return parts .filter(part => part && part !== '/') // 过滤空字符串和单独的斜杠 .join('/') .replace(/\/+/g, '/'); // 替换多个连续斜杠为单个斜杠 } }
2. 关键优化点说明
- 清晰的递归逻辑:用单一递归方法处理所有层级的路由,不管是直接子路由还是懒加载路由,都能逐层遍历。
- 健壮的路径拼接:自动过滤无效路径片段,避免出现
//some-route这类错误路径。 - 高效的路由过滤:只处理带有
data.label的路由,跳过不需要显示的路由(比如重定向路由)。 - 唯一ID生成:用计数器生成菜单项ID,避免原代码中
menuIndex可能重复的问题。
3. 处理未加载的懒加载模块
如果希望菜单包含所有路由(包括还未访问过的懒加载模块),可以通过预加载模块的方式提前获取路由配置。这里推荐每个懒加载模块主动暴露自己的路由配置,避免依赖Angular内部私有API:
比如在some-other-component.module.ts中导出路由:
export const someOtherComponentRoutes: Routes = [ { path: '', component: MultiReaderConfigurationViewComponent, children: [ { path: '', redirectTo: 'child' }, { path: 'child', component: ChildComponent, canActivate: [AuthGuard], resolve: [ChildService], data: { label: 'Child' } }, { path: 'child2', component: Child2Component, canActivate: [AuthGuard], resolve: [Child2Service], data: { label: 'Child2' } } ] } ]; @NgModule({ imports: [RouterModule.forChild(someOtherComponentRoutes)] }) export class SomeOtherComponentModule {}
然后在菜单服务中,你可以在应用初始化时手动导入这些路由配置,或者用APP_INITIALIZER预加载:
// app.module.ts import { APP_INITIALIZER } from '@angular/core'; import { MenuService } from './menu.service'; import { someOtherComponentRoutes } from './some-other-component/some-other-component.module'; import { loginRoutes } from './login.module'; export function preloadAllMenuRoutes(menuService: MenuService) { return () => { // 将所有懒加载模块的路由合并到临时数组 const allRoutes = [ ...menuService.router.config, ...loginRoutes, ...someOtherComponentRoutes ]; // 重新生成菜单 menuService.generateMenu(); return Promise.resolve(); }; } @NgModule({ providers: [ { provide: APP_INITIALIZER, useFactory: preloadAllMenuRoutes, deps: [MenuService], multi: true } ] }) export class AppModule {}
总结
这个优化后的方案不仅能完美处理多层嵌套路由,代码结构更清晰,还降低了对Angular内部API的依赖,同时提升了效率。你可以直接把这个服务集成到你的项目中,根据实际需求调整细节(比如菜单ID生成规则、路径格式等)。
内容的提问来源于stack exchange,提问作者dzsofa
相关产品推荐
相关产品推荐

