Angular中如何根据状态动态渲染Mat Icon 优化冗余*ngIf代码
你遇到的冗余问题本质是把状态分支判断全堆在模板层了,完全可以通过逻辑抽离解决,不需要硬写3个带*ngIf的mat-icon。下面是3种可落地的优化方案,按改造成本从低到高排列:
方案1:组件内收敛判断逻辑(最轻量,零额外封装)
不要在模板里写条件分支,把状态对应的图标名、样式类、提示文案的判断逻辑全部移到组件类中,模板只负责渲染配置,代码量直接减三分之二。
首先在组件TS文件中定义通用配置类型和计算方法:
// 可放在组件类外部做公共类型 interface IconConfig { iconName: string; className: string; tooltip: string; } // 普通审批流状态计算 getApprovalIconConfig(def: any): IconConfig { if (def.idReject != 0) { return { iconName: 'thumb_down', className: 'warn', tooltip: 'Rejected' }; } if (def.idApprove != 0) { return { iconName: 'thumb_up', className: 'success', tooltip: 'Approved' }; } return { iconName: 'help', className: '', tooltip: 'Waiting' }; } // 撤销申诉流状态计算 getCancelIconConfig(def: any): IconConfig { if (def.idCancel < 0) { return { iconName: 'thumb_down', className: 'warn', tooltip: 'Rejected' }; } if (def.idCancel > 0) { return { iconName: 'thumb_up', className: 'success', tooltip: 'Approved' }; } return { iconName: 'help', className: '', tooltip: 'Waiting' }; }
模板直接简化为单个mat-icon,不需要任何*ngIf判断:
<div class="approve-detail" fxLayout="column" fxLayoutGap="10px"> <p>Created: {{absDetail?.reqDate|date:'d. L. y HH:mm'}}</p> <ng-container *ngIf="absDetail?.defList"> <mat-list> <mat-list-item class="def-item" *ngFor="let def of absDetail.defList"> <ng-container *ngIf="getApprovalIconConfig(def) as approvalConfig"> <mat-icon matListIcon [class]="approvalConfig.className" [matTooltip]="approvalConfig.tooltip" > {{approvalConfig.iconName}} </mat-icon> </ng-container> <p matLine>{{def.name}}</p> </mat-list-item> </mat-list> <mat-list *ngIf="abs | isDeleteAppealed:absDetail.defList"> <mat-list-item class="def-item" *ngFor="let def of absDetail.defList"> <ng-container *ngIf="getCancelIconConfig(def) as cancelConfig"> <mat-icon matListIcon [class]="cancelConfig.className" [matTooltip]="cancelConfig.tooltip" > {{cancelConfig.iconName}} </mat-icon> </ng-container> <p matLine>{{def.name}}</p> </mat-list-item> </mat-list> </ng-container> </div>
如果列表数据量超过20条,可以在接口返回defList时就提前把每个条目的iconConfig预处理挂到数据对象上,避免变更检测时重复执行计算方法。
方案2:纯管道封装(性能最优,可复用)
你之前觉得管道不适用是误解——管道不是只能返回字符串,完全可以返回对象类型的配置。纯管道只有在输入值变化时才会重新计算,比模板中调用组件方法的性能更好。
首先写状态转换管道:
import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'approvalIcon', standalone: true }) export class ApprovalIconPipe implements PipeTransform { transform(def: any, type: 'approval' | 'cancel' = 'approval'): IconConfig { if (type === 'approval') { if (def.idReject != 0) return { iconName: 'thumb_down', className: 'warn', tooltip: 'Rejected' }; if (def.idApprove != 0) return { iconName: 'thumb_up', className: 'success', tooltip: 'Approved' }; return { iconName: 'help', className: '', tooltip: 'Waiting' }; } else { if (def.idCancel < 0) return { iconName: 'thumb_down', className: 'warn', tooltip: 'Rejected' }; if (def.idCancel > 0) return { iconName: 'thumb_up', className: 'success', tooltip: 'Approved' }; return { iconName: 'help', className: '', tooltip: 'Waiting' }; } } }
模板用法非常简洁:
<!-- 普通审批状态 --> <mat-icon matListIcon [class]="(def | approvalIcon).className" [matTooltip]="(def | approvalIcon).tooltip" > {{(def | approvalIcon).iconName}} </mat-icon> <!-- 撤销审批状态,传type参数即可 --> <mat-icon matListIcon [class]="(def | approvalIcon:'cancel').className" [matTooltip]="(def | approvalIcon:'cancel').tooltip" > {{(def | approvalIcon:'cancel').iconName}} </mat-icon>
方案3:自定义指令(适合多页面全局复用)
如果这个审批状态图标在系统多个模块都会用到,可以封装成属性指令,把图标、样式、提示的绑定逻辑全部封装在指令内部,模板只需要传数据对象即可。
import { Directive, Input, OnInit, ElementRef, Renderer2 } from '@angular/core'; import { MatIcon } from '@angular/material/icon'; import { MatTooltip } from '@angular/material/tooltip'; @Directive({ selector: '[appApprovalStatus]', standalone: true, hostDirectives: [MatIcon, MatTooltip] }) export class ApprovalStatusDirective implements OnInit { @Input() statusType: 'approval' | 'cancel' = 'approval'; @Input() set appApprovalStatus(def: any) { this._def = def; this.updateStatus(); } private _def: any; constructor( private matIcon: MatIcon, private matTooltip: MatTooltip, private renderer: Renderer2, private el: ElementRef ) {} ngOnInit() { this.renderer.setAttribute(this.el.nativeElement, 'matListIcon', ''); this.updateStatus(); } private updateStatus() { if (!this._def) return; let config: IconConfig; if (this.statusType === 'approval') { config = this._def.idReject != 0 ? { iconName: 'thumb_down', className: 'warn', tooltip: 'Rejected' } : this._def.idApprove != 0 ? { iconName: 'thumb_up', className: 'success', tooltip: 'Approved' } : { iconName: 'help', className: '', tooltip: 'Waiting' }; } else { config = this._def.idCancel < 0 ? { iconName: 'thumb_down', className: 'warn', tooltip: 'Rejected' } : this._def.idCancel > 0 ? { iconName: 'thumb_up', className: 'success', tooltip: 'Approved' } : { iconName: 'help', className: '', tooltip: 'Waiting' }; } this.matIcon.svgIcon = config.iconName; this.matTooltip.message = config.tooltip; this.renderer.setAttribute(this.el.nativeElement, 'class', `mat-icon notranslate material-icons mat-ligature-font mat-icon-no-color ${config.className}`); } }
使用时不需要写多余的属性绑定:
<mat-icon [appApprovalStatus]="def"></mat-icon> <!-- 撤销状态 --> <mat-icon [appApprovalStatus]="def" statusType="cancel"></mat-icon>
选型建议
- 仅当前单页面使用:选方案1,改造成本最低
- 同模块内多处复用:选方案2,管道写法轻量性能好
- 全系统多模块复用:选方案3,封装度最高,后续改规则只需要改指令一处
内容的提问来源于stack exchange,提问作者Vartex05
相关产品推荐
相关产品推荐

