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

Angular动态matMenu含子菜单编译报错:Cannot read property 'subscribe' of undefined

解决Angular Material动态生成带一级子菜单的matMenu编译报错问题

我来帮你排查这个问题,你遇到的TypeError: Cannot read property 'subscribe' of undefined错误,本质是因为MatMenuTrigger找不到对应的菜单实例,再加上模板里还有几个语法和逻辑错误,咱们一步步来修复:

问题分析

你的代码里有几个关键错误:

  1. 动态模板引用变量不合法:Angular不支持#item.subMenuName这种动态生成的模板引用变量,模板引用必须是静态的标识符,这会导致主菜单的matMenuTriggerFor找不到对应的子菜单实例,进而触发内部订阅报错。
  2. 结构指令语法错误:*ngFor="item of menuItems"缺少let关键字,正确写法是*ngFor="let item of menuItems",这个语法错误会直接影响模板编译。
  3. 子菜单按钮缺少指令:子菜单里的按钮没有添加mat-menu-item指令,这会导致Material菜单的交互和样式失效,也可能干扰菜单的内部逻辑。
  4. 变量名笔误:主菜单里的[matMenuTriggerFor]="menuItem.subMenuName"用错了变量名,应该是item.subMenuName,而不是menuItem。

正确实现方案

1. 组件类定义菜单数据

首先确保你的组件类里的菜单数据结构正确,比如:

import { Component, ViewChildren, QueryList, AfterViewInit, MatMenu } from '@angular/core';

@Component({
  selector: 'app-dynamic-menu',
  templateUrl: './dynamic-menu.component.html'
})
export class DynamicMenuComponent implements AfterViewInit {
  // 菜单定义数组,和你给出的结构一致
  menuItems = [
    {
      text: '文件操作',
      subMenuName: 'fileSubMenu',
      subMenuItems: [
        { text: '新建文件', onClick: () => this.handleMenuClick('新建文件') },
        { text: '打开文件', onClick: () => this.handleMenuClick('打开文件') },
        { text: '保存文件', onClick: () => this.handleMenuClick('保存文件') }
      ]
    },
    {
      text: '编辑操作',
      subMenuName: 'editSubMenu',
      subMenuItems: [
        { text: '复制', onClick: () => this.handleMenuClick('复制') },
        { text: '粘贴', onClick: () => this.handleMenuClick('粘贴') }
      ]
    }
  ];

  // 存储子菜单的引用,key是subMenuName
  subMenus: { [key: string]: MatMenu } = {};

  // 获取所有matMenu实例
  @ViewChildren(MatMenu) matMenus!: QueryList<MatMenu>;

  ngAfterViewInit() {
    // 将子菜单实例和subMenuName映射起来
    this.matMenus.forEach(menu => {
      const menuName = menu.id;
      if (menuName) {
        this.subMenus[menuName] = menu;
      }
    });
  }

  // 示例点击事件处理函数
  handleMenuClick(action: string) {
    console.log(`执行操作:${action}`);
  }
}

2. 模板文件编写

然后是HTML模板,我们通过给子菜单设置id来关联subMenuName,再通过@ViewChildren收集菜单实例,最后绑定到主菜单的触发器上:

<!-- 主菜单触发按钮 -->
<button mat-button [matMenuTriggerFor]="mainMenu">打开主菜单</button>

<!-- 主菜单容器 -->
<mat-menu #mainMenu="matMenu">
  <ng-container *ngFor="let item of menuItems">
    <!-- 主菜单项,触发对应的子菜单 -->
    <button mat-menu-item [matMenuTriggerFor]="subMenus[item.subMenuName]">
      {{ item.text }}
    </button>
  </ng-container>
</mat-menu>

<!-- 动态生成子菜单 -->
<ng-container *ngFor="let item of menuItems">
  <mat-menu #subMenu="matMenu" [id]="item.subMenuName">
    <button mat-menu-item *ngFor="let subItem of item.subMenuItems" (click)="subItem.onClick()">
      {{ subItem.text }}
    </button>
  </mat-menu>
</ng-container>

另一种简化实现方式

如果你不想用@ViewChildren,也可以通过(menuOpened)事件直接注册子菜单引用:

<!-- 主菜单部分不变 -->
<button mat-button [matMenuTriggerFor]="mainMenu">打开主菜单</button>
<mat-menu #mainMenu="matMenu">
  <ng-container *ngFor="let item of menuItems">
    <button mat-menu-item [matMenuTriggerFor]="subMenus[item.subMenuName]">
      {{ item.text }}
    </button>
  </ng-container>
</mat-menu>

<!-- 子菜单部分修改为通过事件注册 -->
<ng-container *ngFor="let item of menuItems">
  <mat-menu #subMenu="matMenu" (menuOpened)="subMenus[item.subMenuName] = subMenu">
    <button mat-menu-item *ngFor="let subItem of item.subMenuItems" (click)="subItem.onClick()">
      {{ subItem.text }}
    </button>
  </mat-menu>
</ng-container>

对应的组件类只需要定义subMenus: { [key: string]: MatMenu } = {};,不需要@ViewChildren和ngAfterViewInit。

验证修复

完成以上修改后,编译错误会消失,点击主菜单项时会正确弹出对应的子菜单,点击子菜单项也会触发对应的onClick方法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:18:22