Angular中*ngIf绑定函数/表达式是否会在每次变更检测周期重复执行?
Angular中*ngIf绑定变量的最佳实践疑问解答
我们有一个项目中高频复用的错误提示组件,希望尽可能轻量化,目前有两种实现方式:
方式一:调用工具类方法
<div *ngIf="stringUtils.isNotEmpty(message)"> <error-icon/> <div #errorMsg>{{message}}</div> </div>
对应的工具类代码:
import {Injectable} from '@angular/core'; @Injectable({ providedIn: 'root' }) export class StringUtilsService { isEmpty(str: string | null | undefined) { return !str || String(str)?.trim().length == 0; } // 其他工具方法 }
这种写法简洁清晰,但担心每次变更检测周期(CDC)都会重复调用函数,作为高频复用组件,我们考虑改为内联条件的方式:
方式二:内联条件判断
<div *ngIf="message && message.trim().length > 0"> <error-icon/> <div #errorMsg>{{message}}</div> </div>
针对这两种写法,有两个疑问:
1. 即使message变量本身未发生变更,内联写法中的trim()函数是否仍会在每次变更检测周期被调用?
是的,不管message有没有变化,trim()都会在每次变更检测周期执行。Angular默认的变更检测机制是脏检查,模板中所有绑定的表达式(包括方法调用、函数调用、内联方法),Angular无法提前判断其返回值是否变化,因此每次变更检测都会执行这些表达式。
2. 是否存在既能复用逻辑又避免每次变更检测周期执行的折中方案?比如组件中的getter是否会被同样处理?
首先明确:组件中的getter和模板里的函数调用、内联方法逻辑完全一致,每次变更检测周期都会被调用。因为Angular无法追踪getter内部的依赖关系,只能每次执行getter来获取最新值,所以无法避免重复执行。
推荐两种可行的折中方案:
方案一:使用纯管道(Pure Pipe)
Angular的纯管道只会在输入参数发生变化时重新计算结果,完美适配这种需要复用逻辑且避免重复执行的场景。
实现一个判断字符串非空的纯管道:
import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'isNotEmpty' }) export class IsNotEmptyPipe implements PipeTransform { transform(value: string | null | undefined): boolean { return !!value && value.trim().length > 0; } }
模板中使用管道:
<div *ngIf="message | isNotEmpty"> <error-icon/> <div #errorMsg>{{message}}</div> </div>
方案二:手动缓存计算结果
在组件内监听message的变化,仅当message更新时重新计算判断结果,将结果存在组件属性中,模板直接绑定这个属性。可以通过ngOnChanges或者setter实现:
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; @Component({ selector: 'error-tip', templateUrl: './error-tip.component.html' }) export class ErrorTipComponent implements OnChanges { @Input() message: string | null | undefined; isMessageNotEmpty = false; ngOnChanges(changes: SimpleChanges) { // 仅当message发生变化时重新计算 if (changes['message']) { this.isMessageNotEmpty = !!this.message && this.message.trim().length > 0; } } }
模板中绑定缓存的属性:
<div *ngIf="isMessageNotEmpty"> <error-icon/> <div #errorMsg>{{message}}</div> </div>
内容的提问来源于stack exchange,提问作者Heila Al-Mogren
相关产品推荐
相关产品推荐

