Angular动态matMenu含子菜单编译报错:Cannot read property 'subscribe' of undefined
我来帮你排查这个问题,你遇到的TypeError: Cannot read property 'subscribe' of undefined错误,本质是因为MatMenuTrigger找不到对应的菜单实例,再加上模板里还有几个语法和逻辑错误,咱们一步步来修复:
问题分析
你的代码里有几个关键错误:
- 动态模板引用变量不合法:Angular不支持
#item.subMenuName这种动态生成的模板引用变量,模板引用必须是静态的标识符,这会导致主菜单的matMenuTriggerFor找不到对应的子菜单实例,进而触发内部订阅报错。 - 结构指令语法错误:
*ngFor="item of menuItems"缺少let关键字,正确写法是*ngFor="let item of menuItems",这个语法错误会直接影响模板编译。 - 子菜单按钮缺少指令:子菜单里的按钮没有添加
mat-menu-item指令,这会导致Material菜单的交互和样式失效,也可能干扰菜单的内部逻辑。 - 变量名笔误:主菜单里的
[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
相关产品推荐
相关产品推荐

