Angular Material动态绑定matMenuTriggerFor及表格菜单性能优化问询
我来帮你解决这两个Angular Material的问题,都是实际开发中很常见的场景:
分两种场景来处理,看你是在模板循环里动态生成元素,还是完全手动创建DOM:
场景1:模板内循环生成元素(比如*ngFor)
这是最常见的情况,咱们可以把按钮和对应的菜单放在同一个循环项里,直接用模板引用变量绑定就行,不用搞复杂的动态操作。举个例子:
<ng-container *ngFor="let item of yourDataList"> <!-- 触发按钮 --> <button mat-icon-button [matMenuTriggerFor]="itemMenu"> <mat-icon>more_vert</mat-icon> </button> <!-- 对应的菜单 --> <mat-menu #itemMenu="matMenu"> <button mat-menu-item (click)="doAction(item, 'view')">查看详情</button> <button mat-menu-item (click)="doAction(item, 'edit')">编辑</button> </mat-menu> </ng-container>
每个循环项里的按钮和菜单自动一一绑定,Angular会帮你处理好,简单又靠谱。
场景2:完全动态创建的DOM元素(比如用Renderer2手动添加)
这种情况不能直接给原生DOM绑定指令,因为Angular的指令需要经过编译步骤。咱们可以用模板嵌入的方式来实现:
首先在组件模板里定义一个包含按钮和菜单的模板:
<ng-template #dynamicMenuTpl> <button mat-icon-button [matMenuTriggerFor]="dynamicMenu"> <mat-icon>more_vert</mat-icon> </button> <mat-menu #dynamicMenu="matMenu"> <button mat-menu-item>动态菜单操作</button> </mat-menu> </ng-template>
然后在组件类里,用ViewContainerRef把这个模板动态插入到页面中:
import { Component, TemplateRef, ViewContainerRef, AfterViewInit } from '@angular/core'; @Component({ selector: 'app-dynamic-menu-demo', templateUrl: './dynamic-menu-demo.component.html' }) export class DynamicMenuDemoComponent implements AfterViewInit { @ViewChild('dynamicMenuTpl') dynamicMenuTpl!: TemplateRef<any>; constructor(private vcr: ViewContainerRef) {} ngAfterViewInit() { // 把模板插入到容器里,就能生成带绑定的菜单触发器了 this.vcr.createEmbeddedView(this.dynamicMenuTpl); } }
这样就能动态创建出带有matMenuTriggerFor绑定的元素啦。
这个问题太典型了!几千行表格每行都建一个mat-menu,等于创建几千个组件实例和DOM节点,不卡才怪。核心思路就是复用同一个菜单实例,不要每行都造一个。
具体怎么做呢?看下面的示例:
步骤1:把菜单放到表格外面,只创建一次
不要把mat-menu放在*ngFor的循环里,而是放在表格外,作为全局复用的菜单。
步骤2:点击按钮时传递当前行数据
给每行的按钮绑定点击事件,把当前行的数据存到组件里,同时打开菜单(其实绑定了matMenuTriggerFor后,点击会自动打开,咱们主要是传递数据)。
步骤3:菜单操作基于当前行数据执行
菜单里的点击事件直接用之前存的当前行数据来处理逻辑。
完整代码示例:
组件模板
<!-- 表格部分 --> <table mat-table [dataSource]="bigDataSource" class="mat-elevation-z8"> <!-- 定义其他列 --> <ng-container matColumnDef="name"> <th mat-header-cell *matHeaderCellDef> 名称 </th> <td mat-cell *matCellDef="let row"> {{row.name}} </td> </ng-container> <!-- 操作列 --> <ng-container matColumnDef="actions"> <th mat-header-cell *matHeaderCellDef> 操作 </th> <td mat-cell *matCellDef="let row"> <button mat-icon-button [matMenuTriggerFor]="sharedMenu" (click)="setCurrentRow(row)" > <mat-icon>more_vert</mat-icon> </button> </td> </ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> </table> <!-- 复用的菜单,只创建一次 --> <mat-menu #sharedMenu="matMenu" (closed)="clearCurrentRow()"> <button mat-menu-item (click)="editRow(currentRow)">编辑</button> <button mat-menu-item (click)="deleteRow(currentRow)">删除</button> </mat-menu>
组件类
import { Component } from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; @Component({ selector: 'app-table-menu-performance', templateUrl: './table-menu-performance.component.html' }) export class TableMenuPerformanceComponent { displayedColumns = ['name', 'actions']; // 模拟一万行数据 bigDataSource = new MatTableDataSource( Array.from({length: 10000}, (_, i) => ({ id: i + 1, name: `数据项 ${i + 1}` })) ); currentRow: any; setCurrentRow(row: any) { // 点击按钮时,把当前行数据存下来 this.currentRow = row; } clearCurrentRow() { // 菜单关闭后,清空当前行数据,避免内存泄漏 this.currentRow = null; } editRow(row: any) { console.log('编辑数据:', row); // 这里写你的编辑逻辑 } deleteRow(row: any) { console.log('删除数据:', row); // 这里写你的删除逻辑 } }
这样不管表格有多少行,都只有一个mat-menu实例,DOM节点数量瞬间减少几千个,页面加载速度和渲染性能会提升非常明显!另外,要是你的组件用了OnPush变更检测策略,性能还能再上一个台阶。
内容的提问来源于stack exchange,提问作者Sagar Yadav

