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

Angular Material通用数据网格模板渲染优化方案咨询

通用Angular Material数据网格组件的模板复用优化问题

我是一名初级开发者,用Angular Material构建了通用数据网格组件,在多个组件中复用,每次传入对应数据。部分列需要渲染img、button、input这类自定义模板。

尝试用ng-template实现,但遇到两个问题:

  • 必须为每个列定义专属的ng-template,不同组件的模板各不相同,扩展性差
  • 触发数据相关函数时,无法传递当前行的数据参数

想知道当前实现是否合理,有没有优化建议?

相关代码

gridData.ts

@ContentChild('one') one!: TemplateRef<ElementRef>;
@ContentChild('two') two!: TemplateRef<ElementRef>;
@ContentChild('three') three!: TemplateRef<ElementRef>;

gridData.html

<td *matCellDef="let element" mat-cell [style.width]="header.width">
    <div *ngIf="!header.template && header.type!='img'">{{element[header.fieldName]}}</div>
    <div *ngIf="!header.template && header.type=='img'">
       <img src="../../../assets/Images/{{element[header.fieldName]}}.jpg" class="img">
    </div>
    <div *ngIf="header.template && header.template=='one'">
      <ng-template #f [ngTemplateOutlet]="one"></ng-template>
    </div>
    <div *ngIf="header.template && header.template=='two'">
      <ng-template #f [ngTemplateOutlet]="two"></ng-template>
    </div>
    <div *ngIf="header.template && header.template=='three'">
      <ng-template #f [ngTemplateOutlet]="three"></ng-template>
    </div>
</td>

产品组件(product.ts)

this.gridServ.initialHeadings = [
  { fieldName: 'iProductId', display: 'קוד', type: 'string', width: '5%' },
  { fieldName: '', display: "הוסף/הסר ממאגר", type: 'string', width: '10%', template:'one' },
  { fieldName: 'iProductId', display: "תמונה", type: 'img', width: '7%' },
  { fieldName: '', display: "פרטים", type: 'string', width: '4%', template:'two' },
  { fieldName: '', display: "הסטוריה", type: 'string', width: '4%', template:'three' },
  { fieldName: 'nvProductName', display: "שם מוצר", type: 'string', width: '12%' },
  { fieldName: 'nvCompanyName', display: "שם חברה", type: 'string', width: '10%' },
  { fieldName: 'nvCommet', display: "הערה", type: 'string', width: '13%' },
  { fieldName: "nvProductCategortName", display: "קטגוריה", type: 'string', width: '10%' }
];
this.gridServ.data = data;

product.html

<app-data-grid *ngIf="showGrid">
  <ng-template #one><img src="../../../../assets/Images/Delete.png" (click)="ddd($event)"></ng-template>
  <ng-template #two><img src="../../../../assets/Images/edit.png" (click)="editProduct(3)"></ng-template>
  <ng-template #three><img src="../../../../assets/Images/plus.png"></ng-template>
</app-data-grid>

优化方案

1. 传递行数据到自定义模板,解决参数传递问题

当前模板无法拿到行数据,是因为ngTemplateOutlet没传递上下文。修改grid组件的模板渲染逻辑,把当前行的element传递出去:

修改gridData.html中模板渲染的部分:

<div *ngIf="header.template">
  <ng-template 
    [ngTemplateOutlet]="getTemplate(header.template)" 
    [ngTemplateOutletContext]="{ $implicit: element }">
  </ng-template>
</div>

然后在外部使用模板时,通过let-element接收行数据:

<app-data-grid *ngIf="showGrid">
  <ng-template #one let-element>
    <img src="../../../../assets/Images/Delete.png" (click)="ddd(element)">
  </ng-template>
  <ng-template #two let-element>
    <img src="../../../../assets/Images/edit.png" (click)="editProduct(element.iProductId)">
  </ng-template>
</app-data-grid>

这样就能在点击事件里直接拿到当前行的完整数据,不用写死参数。

2. 动态匹配模板,摆脱固定名称限制

当前需要提前在grid组件里定义@ContentChild('one')这类固定引用,扩展性差。可以改成用@ContentChildren批量获取所有传入的模板,再通过标识匹配:

修改gridData.ts

import { ContentChildren, QueryList, TemplateRef } from '@angular/core';

@ContentChildren(TemplateRef) templates!: QueryList<TemplateRef<any>>;

// 新增方法:根据模板名称找到对应的TemplateRef
getTemplate(templateName: string): TemplateRef<any> {
  // 给外部模板添加标识,比如用id属性区分
  return this.templates.find(tpl => {
    const element = tpl.elementRef.nativeElement;
    return element.id === `template-${templateName}`;
  })!;
}

修改product.html的模板,添加id标识

<app-data-grid *ngIf="showGrid">
  <ng-template #one id="template-one" let-element>
    <img src="../../../../assets/Images/Delete.png" (click)="ddd(element)">
  </ng-template>
  <ng-template #two id="template-two" let-element>
    <img src="../../../../assets/Images/edit.png" (click)="editProduct(element.iProductId)">
  </ng-template>
</app-data-grid>

这样不管新增多少自定义模板,都不用修改grid组件的代码,扩展性大幅提升。

3. 简化列渲染逻辑,减少冗余代码

当前用多个*ngIf分支判断,代码冗余。可以用ngSwitch替代:

修改gridData.html:

<td *matCellDef="let element" mat-cell [style.width]="header.width">
  <ng-container [ngSwitch]="true">
    <!-- 普通文本列 -->
    <ng-container *ngSwitchCase="!header.template && header.type!='img'">
      {{element[header.fieldName]}}
    </ng-container>
    <!-- 图片列 -->
    <ng-container *ngSwitchCase="!header.template && header.type=='img'">
      <img src="../../../assets/Images/{{element[header.fieldName]}}.jpg" class="img">
    </ng-container>
    <!-- 自定义模板列 -->
    <ng-container *ngSwitchCase="header.template">
      <ng-template 
        [ngTemplateOutlet]="getTemplate(header.template)" 
        [ngTemplateOutletContext]="{ $implicit: element }">
      </ng-template>
    </ng-container>
  </ng-container>
</td>

进阶优化:直接在列配置中传入模板

如果想更简洁,可以把模板直接绑定到列配置里,不用字符串匹配:

修改product.ts,先获取模板引用

import { ViewChild, TemplateRef } from '@angular/core';

// 在组件中获取模板引用
@ViewChild('one') oneTemplate!: TemplateRef<any>;
@ViewChild('two') twoTemplate!: TemplateRef<any>;

// 列配置中直接传入TemplateRef
this.gridServ.initialHeadings = [
  // ...其他列
  { fieldName: '', display: "הוסף/הסר ממאגר", type: 'string', width: '10%', template: this.oneTemplate },
  { fieldName: '', display: "פרטים", type: 'string', width: '4%', template: this.twoTemplate },
  // ...其他列
];

修改gridData.ts,去掉ContentChildren,直接使用传入的模板

// 不需要@ContentChild或@ContentChildren
// 直接在渲染时使用header.template

修改gridData.html的模板渲染部分

<ng-container *ngSwitchCase="header.template">
  <ng-template 
    [ngTemplateOutlet]="header.template" 
    [ngTemplateOutletContext]="{ $implicit: element }">
  </ng-template>
</ng-container>

这种方式更直观,不需要额外的匹配逻辑,适合模板和列配置在同一个组件中定义的场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 01:31:04