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

基于嵌套Router-Outlet布局,如何无内容投影实现动态工具栏菜单?

不用内容投影的动态菜单实现方案(适配嵌套router-outlet)

给你两个实用的实现方案,完全符合嵌套路由的规范,不需要依赖内容投影:


方案一:路由数据驱动(简洁易维护,和路由强绑定)

这种方案把动态菜单配置直接写在路由里,路由切换时自动更新工具栏,适合菜单逻辑简单(比如跳转路由)的场景。

1. 修改路由配置

给需要显示动态菜单的子路由添加data字段,存储菜单的标签、跳转路径等信息:

// 无工具栏
{ path: ROUTENAMES.LOGIN, component: LoginPageComponent },

// 包含工具栏
{
    path: '',
    component: AppLayoutComponent, // 引入工具栏
    children: [
        { 
            path: ROUTENAMES.LIST, 
            component: ListComponent,
            data: {
                toolbarActions: [
                    { label: '新增对象', routerLink: [ROUTENAMES.EDIT] }
                ]
            }
        },
        { 
            path: ROUTENAMES.EDIT, 
            component: EditComponent,
            data: {
                toolbarActions: [
                    { label: '返回列表', routerLink: [ROUTENAMES.LIST] }
                ]
            }
        },
        { path: ROUTENAMES.OTHER, component: OtherComponent },
    ]
}

2. 在AppLayoutComponent中监听路由变化,渲染动态菜单

通过订阅路由事件,获取当前激活路由的data字段,然后在模板中遍历渲染菜单:

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
import { filter, map } from 'rxjs/operators';

// 定义菜单类型
interface ToolbarAction {
  label: string;
  routerLink?: string[];
  onClick?: () => void;
}

@Component({
  selector: 'app-layout',
  template: `
    <div class="toolbar">
      <!-- 通用菜单项 -->
      <button (click)="logout()">退出登录</button>
      <button (click)="profile()">个人资料</button>
      
      <!-- 动态菜单项 -->
      <button 
        *ngFor="let action of dynamicActions$ | async"
        [routerLink]="action.routerLink"
        (click)="action.onClick?.()"
      >
        {{ action.label }}
      </button>
    </div>
    <router-outlet></router-outlet>
  `
})
export class AppLayoutComponent implements OnInit {
  // 监听路由变化,获取动态菜单
  dynamicActions$ = this.router.events.pipe(
    filter(event => event instanceof NavigationEnd),
    map(() => {
      let activeRoute = this.activatedRoute.firstChild;
      // 适配多层子路由的情况,找到最深的激活路由
      while (activeRoute?.firstChild) {
        activeRoute = activeRoute.firstChild;
      }
      // 返回当前路由的动态菜单,没有则返回空数组
      return activeRoute?.data?.['toolbarActions'] || [];
    })
  );

  constructor(
    private router: Router,
    private activatedRoute: ActivatedRoute
  ) {}

  ngOnInit() {}

  logout() { /* 实现退出登录逻辑 */ }
  profile() { /* 实现个人资料跳转逻辑 */ }
}

方案二:共享服务通信(灵活适配复杂逻辑)

如果你的动态菜单需要和组件内部状态联动(比如根据列表选中状态禁用按钮),可以用共享服务来实现组件间通信。

1. 创建Toolbar共享服务

用BehaviorSubject来存储当前的动态菜单,提供设置和清空菜单的方法:

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

// 定义菜单类型
export interface ToolbarAction {
  label: string;
  onClick: () => void;
  disabled?: boolean;
}

@Injectable({ providedIn: 'root' })
export class ToolbarService {
  private actionsSubject = new BehaviorSubject<ToolbarAction[]>([]);
  // 暴露可观察对象供父组件订阅
  actions$ = this.actionsSubject.asObservable();

  // 设置动态菜单
  setActions(actions: ToolbarAction[]) {
    this.actionsSubject.next(actions);
  }

  // 清空动态菜单(组件销毁时调用)
  clearActions() {
    this.actionsSubject.next([]);
  }
}

2. 在子组件中注册/清理动态菜单

在组件初始化时调用服务的setActions方法注册菜单,销毁时调用clearActions避免内存泄漏:

ListComponent示例:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { ToolbarService } from './toolbar.service';
import { ROUTENAMES } from './route-names';

@Component({
  selector: 'app-list',
  template: `<!-- 列表内容模板 -->`
})
export class ListComponent implements OnInit, OnDestroy {
  constructor(
    private toolbarService: ToolbarService,
    private router: Router
  ) {}

  ngOnInit() {
    this.toolbarService.setActions([
      {
        label: '新增对象',
        onClick: () => this.router.navigate([ROUTENAMES.EDIT])
      }
    ]);
  }

  ngOnDestroy() {
    this.toolbarService.clearActions();
  }
}

EditComponent示例:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { ToolbarService } from './toolbar.service';
import { ROUTENAMES } from './route-names';

@Component({
  selector: 'app-edit',
  template: `<!-- 编辑内容模板 -->`
})
export class EditComponent implements OnInit, OnDestroy {
  constructor(
    private toolbarService: ToolbarService,
    private router: Router
  ) {}

  ngOnInit() {
    this.toolbarService.setActions([
      {
        label: '返回列表',
        onClick: () => this.router.navigate([ROUTENAMES.LIST])
      }
    ]);
  }

  ngOnDestroy() {
    this.toolbarService.clearActions();
  }
}

3. 在AppLayoutComponent中订阅服务,渲染菜单

import { Component } from '@angular/core';
import { ToolbarService } from './toolbar.service';

@Component({
  selector: 'app-layout',
  template: `
    <div class="toolbar">
      <!-- 通用菜单项 -->
      <button (click)="logout()">退出登录</button>
      <button (click)="profile()">个人资料</button>
      
      <!-- 动态菜单项 -->
      <button 
        *ngFor="let action of actions$ | async"
        (click)="action.onClick()"
        [disabled]="action.disabled"
      >
        {{ action.label }}
      </button>
    </div>
    <router-outlet></router-outlet>
  `
})
export class AppLayoutComponent {
  // 订阅服务的动态菜单数据
  actions$ = this.toolbarService.actions$;

  constructor(
    private toolbarService: ToolbarService
  ) {}

  logout() { /* 实现退出登录逻辑 */ }
  profile() { /* 实现个人资料跳转逻辑 */ }
}

方案选择建议

  • 如果菜单只是简单的路由跳转或无状态操作,优先用方案一,配置和路由绑定,维护成本低。
  • 如果菜单需要和组件内部状态联动(比如根据数据状态禁用/显示),用方案二,灵活性更高。

内容的提问来源于stack exchange,提问作者NoRyb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:19:53