Angular自定义文本字段调用form.reset()触发RangeError调用栈溢出错误
问题根因
你触发死循环的核心原因是自定义组件内部绑定的formControl和父组件传入的外部FormControl是同一个实例:
- 父组件调用
FormGroup.reset()时,外部FormControl值变更,触发Angular调用自定义组件ControlValueAccessor接口的writeValue方法 writeValue内你又给同一个实例调用setValue/patchValue,即使设置了emitEvent: false,Angular表单的重置状态更新逻辑仍会再次触发writeValue,形成无限递归最终栈溢出。
解决方案
推荐两种修复方式,按需选择即可:
方案1:移除冗余内部FormControl,直接实现CVA接口
最简洁的修复方式,直接绑定输入框的值,避免两个控件实例混淆的问题:
1. 修改组件TS代码
import { Component, Input, Optional, Self } from '@angular/core'; import { ControlValueAccessor, NgControl } from '@angular/forms'; @Component({ selector: 'input-text-field', templateUrl: './input-text-field.component.html', styleUrls: ['./input-text-field.component.scss'] }) export class InputTextFieldComponent implements ControlValueAccessor { public value = ''; public onChange = (value: any) => {}; public onTouched = () => {}; @Input() public fieldId: string; @Input() public isRequired: boolean; @Input() public fieldClass: string; @Input() public labelText: string; @Input() public tabNumber: number; constructor(@Self() @Optional() public ngControl: NgControl) { if(this.ngControl) { this.ngControl.valueAccessor = this; } } registerOnChange(fn: (value: any) => void): void { this.onChange = fn; } registerOnTouched(fn: () => void): void { this.onTouched = fn; } writeValue(value: any): void { this.value = value ?? ''; } }
2. 修改组件HTML模板
<mat-form-field> <mat-label><span *ngIf="isRequired" class="input-required">*</span> {{labelText}}</mat-label> <input type="text" matInput [value]="value" (input)="onChange($event.target.value)" (blur)="onTouched()" [id]="fieldId" [class]="fieldClass" [tabIndex]="tabNumber" > </mat-form-field>
方案2:保留内部FormControl,分离内外实例
如果你需要用内部FormControl做自定义验证、状态管理,就不要替换内部实例,仅做双向值和状态同步即可:
1. 修改ngAfterViewInit逻辑
删除原有实例赋值代码,改为状态同步:
ngAfterViewInit(): void { if (this.ngControl?.control) { // 同步外部初始值到内部 this._formControl.setValue(this.ngControl.control.value, { emitEvent: false }); // 同步内部值变化到外部 this._formControl.valueChanges.subscribe(val => { this.onChange(val); }); // 同步外部验证状态到内部(让mat-form-field正确显示错误) this.ngControl.control.statusChanges.subscribe(() => { if (this.ngControl.control.touched && this.ngControl.control.invalid) { this._formControl.setErrors(this.ngControl.control.errors); this._formControl.markAsTouched(); } else { this._formControl.setErrors(null); } }); } }
2. 保留原有writeValue逻辑即可
writeValue(value: any): void { if(this._formControl) this._formControl.setValue(value, { emitEvent: false }); }
两种方案都可以彻底解决reset触发的死循环问题,推荐优先用方案1,代码更简洁易维护。
内容的提问来源于stack exchange,提问作者mdailey77
相关产品推荐
相关产品推荐

