Angular自动补全ngModelChange传值undefined触发变更检查报错
问题场景
在对接API的Angular matAutocomplete自动补全组件时,预期实现用户开始输入后才返回搜索结果的效果,但在输入框键入若干字符后,控制台抛出如下错误:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'xxxx'. Current value: 'undefined'.
已尝试使用变更检测相关方案修复该问题,但未生效。经初步排查,核心问题疑似与@Input传入的collectFieldValue函数有关:移除该函数调用后,控制台打印的ngModelChange入参value从未出现undefined的情况。
相关代码
Search.component.html
<input type="text" class="searchItem" placeholder="Begin your’" aria-label="Begin your search" matInput [matAutocomplete]="auto" (ngModelChange)="inputCheck($event)" [(ngModel)]="searchInput" (focusout)="onFocusOut()" (focus)="onFocus()" (keydown)="onKeyDown()" >
Search.component.ts
@Input() setSelectedLocation!: Function; inputCheck(value: any) { if (value !== undefined && value !== "" && value !== null) { this.collectFieldValue(value, this.sharedService.sharedVal && this.sharedService.sharedVal !== "" ? this.sharedService.sharedVal : undefined) } }
Main.component.html
<app-search-box [collectFieldValue]="triggerLocationSearch" [setSelectedLocation]="setSelectedLocation"> </app-search-box>
问题根因
这个报错和变更检测触发时机没有关系,核心是父组件传入的方法丢失了正确的this上下文,执行时产生了非预期的跨组件属性污染:
- 直接通过
@Input传递父组件的普通类方法triggerLocationSearch时,方法在子组件内被调用的瞬间,内部的this指针会从父组件实例偏移到子组件实例。 - 如果
triggerLocationSearch方法内部存在对自身上下文属性的读写操作,这些操作会直接作用在子组件实例上,一旦刚好修改到子组件用于双向绑定的searchInput属性、或者matAutocomplete依赖的选项列表属性,就会导致Angular完成一轮变更检测校验后,又发现模板绑定值从之前的输入值变成了undefined,直接抛出上述错误。 - 观察到的“移除函数调用后value就不会出现undefined”的现象可以直接印证这个逻辑:undefined值不是用户输入产生的,是传入的方法执行时错误修改子组件属性触发的。
修复方案
- 优先使用Angular标准的组件通信方式,从根源避免上下文丢失问题:
- 子组件内不要用
@Input接收函数类型参数,改为用@Output事件发射器传递值:将@Input() collectFieldValue: Function替换为@Output() collectFieldValue = new EventEmitter<{inputVal: any, sharedVal: any}>() - 子组件原先调用传入函数的位置,改为触发事件:
this.collectFieldValue.emit({inputVal: value, sharedVal: this.sharedService.sharedVal && this.sharedService.sharedVal !== "" ? this.sharedService.sharedVal : undefined}) - 父组件模板内改为监听事件触发对应方法:
<app-search-box (collectFieldValue)="triggerLocationSearch($event.inputVal, $event.sharedVal)" [setSelectedLocation]="setSelectedLocation"></app-search-box>
- 子组件内不要用
- 如果暂时不想重构为
@Output写法,需要在传参时固定方法的this指向,避免上下文偏移:
可以在父组件类中把triggerLocationSearch定义为箭头函数类属性,固定this绑定到父组件实例;也可以在模板传参时手动绑定上下文,写成[collectFieldValue]="triggerLocationSearch.bind(this)"。 - 额外校验:检查
triggerLocationSearch方法内部是否存在隐式修改入参、或操作和子组件同名属性的逻辑,去掉不必要的副作用。
内容的提问来源于stack exchange,提问作者A.Mac
相关产品推荐
相关产品推荐

