Angular如何不触发父组件变更检测渲染模板获取文本值?
我有如下模板代码:
<app-filterable-select formControlName="customer" [options]="customers" [filterFn]="filterCustomer" > <ng-template let-customer>{{ customer.name }}, {{ customer.location }}</ng-template> </app-filterable-select>
想要在filterable-select组件中实现一个方法,复用用户自定义模板来返回当前选中值的文本,于是写了以下代码:
protected get triggerValue() { if (this.selected) { const e = this.contentRef.createEmbeddedView({ $implicit: this.selected }); e.detectChanges(); return e.rootNodes[0].textContent; } return ""; }
模板引用通过@ContentChild(TemplateRef) protected contentRef!: TemplateRef<unknown>;获取,但运行时触发了报错:
ERROR Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'Customer...'. It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook?
尝试调用e.detach()也无法解决问题,需要找到不触发变更检测报错的解决方案。
方法1:手动控制变更检测并销毁视图
在组件中注入ChangeDetectorRef,创建视图后手动完成变更检测,同时立即销毁视图避免内存泄漏:
import { ChangeDetectorRef } from '@angular/core'; constructor(private cdr: ChangeDetectorRef) {} protected get triggerValue() { if (this.selected) { const e = this.contentRef.createEmbeddedView({ $implicit: this.selected }); // 手动触发变更检测,同步视图状态 this.cdr.detectChanges(); const text = e.rootNodes[0].textContent; // 立即销毁嵌入式视图 e.destroy(); return text || ""; } return ""; }
方法2:创建脱离组件变更检测树的视图
创建嵌入式视图时指定OnPush策略,让视图不加入当前组件的变更检测树,避免触发检测冲突:
import { ChangeDetectionStrategy } from '@angular/core'; protected get triggerValue() { if (this.selected) { // 创建视图时指定独立的变更检测策略 const e = this.contentRef.createEmbeddedView( { $implicit: this.selected }, { changeDetection: ChangeDetectionStrategy.OnPush } ); e.detectChanges(); const text = e.rootNodes[0].textContent; e.destroy(); return text || ""; } return ""; }
方法3:使用离线DOM容器渲染
通过Renderer2创建离线DOM容器,将模板渲染到容器中,完全隔离组件的变更检测流程:
import { Renderer2 } from '@angular/core'; constructor(private renderer: Renderer2) {} protected get triggerValue() { if (this.selected) { // 创建离线div作为渲染容器 const container = this.renderer.createElement('div'); // 将视图渲染到离线容器中 const e = this.contentRef.createEmbeddedView({ $implicit: this.selected }, null, container); e.detectChanges(); const text = container.textContent; // 清理视图和容器 e.destroy(); this.renderer.removeChild(null, container); return text || ""; } return ""; }
内容的提问来源于stack exchange,提问作者Elias

