Angular Material通用数据表:无需Angular函数实现深层绑定
Angular Material通用表格深层数据绑定替代方案
问题场景
我基于Angular Material实现了一个通用表格组件,列元数据定义如下:
tableColumnBinding: TableColumnMetadata[] = [ { title: 'product.id', name: 'id', type: 'link', path: 'product.status.value', // 深层嵌套路径 icon: '', isDeepBinding: true }]
对应HTML视图代码:
<ng-container *ngFor="let columnMetadata of tableMetadata" [matColumnDef]="columnMetadata.name"> <th mat-header-cell *matHeaderCellDef> {{ columnMetadata.title | transloco}} </th> <td mat-cell *matCellDef="let element "> <div *ngIf="columnMetadata.isDeepBinding; else isNotDeepBinding"> <span (click)="onNavigateButtonClick(element)"> {{columnDeepDataBinding(element, columnMetadata.path)}}</span> </div> </td> </ng-container>
当前通过组件类中的columnDeepDataBinding函数实现深层数据绑定,希望找到无需调用组件函数的替代方案,比如类似element[columnMetadata.path]的声明式写法。
解决方案
1. 自定义纯管道(推荐)
创建纯管道解析深层路径,既符合Angular声明式风格,又避免组件函数在每次变更检测时重复执行的性能问题。
步骤1:实现纯管道
import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'deepAccess', pure: true // 默认就是纯管道,显式声明更清晰 }) export class DeepAccessPipe implements PipeTransform { transform(obj: Record<string, any>, path: string): any { if (!obj || !path) return null; // 拆分路径并递归取值 return path.split('.').reduce((currentValue, key) => { return currentValue ? currentValue[key] : null; }, obj); } }
步骤2:在模块中声明管道
将DeepAccessPipe添加到组件所在模块的declarations数组中。
步骤3:模板中使用管道
替换原函数调用,直接用管道解析数据:
<div *ngIf="columnMetadata.isDeepBinding; else isNotDeepBinding"> <span (click)="onNavigateButtonClick(element)"> {{ element | deepAccess: columnMetadata.path }} </span> </div>
2. 基于Lodash的简化实现(若项目已引入Lodash)
如果项目已使用Lodash,可利用_.get方法简化管道逻辑:
import { Pipe, PipeTransform } from '@angular/core'; import { get } from 'lodash'; @Pipe({ name: 'deepAccess' }) export class DeepAccessPipe implements PipeTransform { transform(obj: Record<string, any>, path: string): any { return get(obj, path); } }
模板用法和方案1完全一致。
3. 模板内递归解析(无额外管道/函数)
若不想创建管道,可通过递归模板实现深层路径解析,完全在模板内完成逻辑:
<ng-container *ngFor="let columnMetadata of tableMetadata" [matColumnDef]="columnMetadata.name"> <th mat-header-cell *matHeaderCellDef> {{ columnMetadata.title | transloco}} </th> <td mat-cell *matCellDef="let element "> <div *ngIf="columnMetadata.isDeepBinding; else isNotDeepBinding"> <span (click)="onNavigateButtonClick(element)"> <!-- 递归模板定义 --> <ng-template #deepValue let-data="data" let-segments="segments"> <ng-container *ngIf="segments.length > 0; else showValue"> <ng-container *ngIf="data[segments[0]]; else empty"> <!-- 递归调用模板,截断路径数组 --> <ng-container *ngTemplateOutlet="deepValue; context: { data: data[segments[0]], segments: segments.slice(1) }"></ng-container> </ng-container> </ng-container> <ng-template #showValue>{{ data }}</ng-template> <ng-template #empty>-</ng-template> </ng-template> <!-- 初始化递归模板,拆分路径为数组 --> <ng-container *ngTemplateOutlet="deepValue; context: { data: element, segments: columnMetadata.path.split('.') }"></ng-container> </span> </div> </td> </ng-container>
这种方式无需额外代码,但可读性较差,适合路径层级较少的场景。
内容的提问来源于stack exchange,提问作者Saddamhussain
相关产品推荐
相关产品推荐

