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
相关产品推荐
相关产品推荐

