Angular基于ControlValueAccessor自定义表单控件错误消息显示方法
问题原因
自定义控件无法显示错误的核心原因是:组件内部textInputFormControl是独立创建的实例,和外层表单绑定的FormControl没有做校验状态、错误信息的同步,外层配置的校验规则产生的错误根本不会传到内部控件里,自然拿不到要展示的错误消息。
实现方案
最简便的实现方式是直接注入NgControl拿到外层绑定的表单控件实例,替代内部独立创建的FormControl,不需要手动做值和状态的中转,所有校验状态、错误信息、值变更都会自动同步。
注意:需要删掉组件装饰器里providers数组中NG_VALUE_ACCESSOR的配置,否则会触发循环依赖报错。
修改后的组件代码
text-input.component.ts
import { Component, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, FormControl, NgControl } from '@angular/forms'; import { getErrorMessage } from '../form-fields.helper'; @Component({ selector: 'x-text-input', templateUrl: './text-input.component.html', styleUrls: ['./text-input.component.scss'] }) export class TextInputComponent implements ControlValueAccessor { @Input() label: string; @Input() readonly: boolean; @Input() required: boolean; textInputFormControl: FormControl; onTouched = () => {}; onChange: (_: string) => {}; constructor(@Optional() @Self() public ngControl: NgControl) { if (this.ngControl) { this.ngControl.valueAccessor = this; } } ngOnInit(): void { // 直接复用外层绑定的FormControl实例,所有状态自动同步 this.textInputFormControl = this.ngControl.control as FormControl; } writeValue(value: any): void { this.textInputFormControl?.setValue(value, { emitEvent: false }); } registerOnChange(fn: any): void { this.onChange = fn; } registerOnTouched(fn: any): void { this.onTouched = fn; } setDisabledState(disabled: boolean): void { disabled ? this.textInputFormControl?.disable({ emitEvent: false }) : this.textInputFormControl?.enable({ emitEvent: false }); } getErrorText(): string { return getErrorMessage(this.textInputFormControl.errors); } }
text-input.component.html
<div style="font-size: 12px"> <mat-form-field appearance="outline"> <mat-label>{{ label }}</mat-label> <input matInput [required]="required" [readonly]="readonly" [formControl]="textInputFormControl" (blur)="onTouched()" /> <!-- 错误提示:控件失焦且校验不通过时显示 --> <mat-error *ngIf="textInputFormControl.invalid && textInputFormControl.touched"> {{ getErrorText() }} </mat-error> </mat-form-field> </div>
注意事项
- 原来模板里绑定的
(input)="change()"可以删掉,因为直接复用了外层的FormControl实例,值变更会自动同步,不需要手动调用onChange传值。 - 给输入框加了
(blur)="onTouched()"事件,保证用户输入完成失焦后才触发错误提示,避免输入过程中就弹出错误。 - 补上了之前缺失的
@Input() required声明,否则模板里的required绑定不会生效。 - 这种实现方式和普通Angular Material输入框的行为完全一致,不管外层用
[formControl]还是formControlName绑定,所有内置校验、自定义校验产生的错误都能自动获取到,不需要额外给组件传错误参数。
内容的提问来源于stack exchange,提问作者Athul aravind k
相关产品推荐
相关产品推荐

