自定义实现MatFormFieldControl与ControlValueAccessor的Angular控件无法触发mat-error显示
你遇到的问题其实是自定义MatFormFieldControl控件时,没有正确同步表单提交状态到控件的错误状态判断逻辑里,导致点击提交按钮时,mat-form-field没识别到应该显示错误提示——只有控件失焦(被标记为touched)时才触发了错误显示。我来一步步帮你解决:
问题根源
默认情况下,mat-error的显示依赖于控件的错误状态(errorState),这个状态由ErrorStateMatcher或者自定义MatFormFieldControl的errorState getter决定。你的自定义控件没正确实现errorState,导致表单提交时,控件虽然无效,但mat-form-field不知道要触发错误显示。
解决方案
1. 在自定义控件中注入FormGroupDirective
我们需要获取表单的提交状态,所以先在自定义控件的构造函数中注入它:
import { FormGroupDirective } from '@angular/forms'; // ... constructor( private _formGroupDirective: FormGroupDirective, private _cdr: ChangeDetectorRef, // 记得注入变更检测器 // 保留你已有的其他注入项 ) {}
2. 正确实现errorState getter
在自定义控件类中添加errorState的getter方法,结合控件的无效状态、触摸状态和表单提交状态:
private touched = false; // 维护控件的触摸状态 get errorState(): boolean { // 规则:控件无效 且 (控件已触摸 或 表单已提交) return this.FormGrp.invalid && (this.touched || this._formGroupDirective.submitted); }
3. 维护触摸状态并触发状态更新
给输入框添加失焦事件,标记控件为已触摸,同时通知mat-form-field更新状态:
<!-- 自定义控件模板 --> <div [formGroup]="FormGrp"> <input formControlName="data" (input)="input()" (blur)="onTouched()" /> </div>
然后在控件类中实现onTouched方法:
onTouched(): void { if (!this.touched) { this.touched = true; this.stateChanges.next(); // 通知mat-form-field状态变化 this._cdr.markForCheck(); // 触发变更检测 } }
另外,还要监听表单提交和控件内部状态变化,确保状态更新能被及时检测到:
constructor( private _formGroupDirective: FormGroupDirective, private _cdr: ChangeDetectorRef, // 其他注入项 ) { // 监听表单提交事件 this._formGroupDirective.ngSubmit.subscribe(() => { this._cdr.markForCheck(); }); // 监听控件内部表单组的状态变化 this.FormGrp.statusChanges.subscribe(() => { this._cdr.markForCheck(); }); }
4. 优化模板中的mat-error条件(可选)
如果已经正确实现了errorState,其实可以简化mat-error的显示条件,甚至不用手动判断:
<mat-error>required</mat-error>
mat-form-field会自动根据errorState控制mat-error的显示隐藏。如果你想保留手动判断,也可以写成更严谨的形式:
<mat-error *ngIf="formGroup.get('testControl').invalid && (formGroup.get('testControl').touched || formGroup.submitted)"> required </mat-error>
验证效果
完成以上修改后,点击提交按钮时,表单会被标记为submitted,自定义控件的errorState会返回true,此时mat-error就会正确显示出来,不需要等到控件失焦。
备注:内容来源于stack exchange,提问作者Paul

