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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 04:48:17