You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular7如何获取所有嵌套子路由?现有实现不支持多层路由

优化Angular多层嵌套路由的菜单生成逻辑

我懂你现在的困扰——基于Adrian Paul的方案改完后,单层子路由能正常生成菜单,可多层嵌套就失效了,而且当前的实现不仅代码冗余,处理深层路由时效率也很低。咱们来一步步优化这个逻辑。

现有代码的核心问题

  1. 依赖Angular内部私有属性:直接用_loadedConfig这个未公开的API,未来Angular版本更新可能会导致代码失效。
  2. 递归逻辑混乱:子路由和懒加载路由的处理分散在多个方法里,多层嵌套时容易遗漏层级。
  3. 路径拼接不健壮:简单的字符串拼接容易产生//这类无效路径,或者层级错误。
  4. 重复遍历过滤:多处重复执行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:16:12