Angular Material自定义表单字段mat-error无法显示问题求解
Angular Material自定义表单组件mat-error不显示修复方案
核心问题梳理
- 模板中同时对input使用
[(ngModel)]和[value]绑定冲突,导致内部输入状态更新异常 MatFormFieldControl接口仅声明未正确实现,stateChanges无状态更新通知,MatFormField无法感知错误状态变化- 内部输入框状态未和父组件表单的自定义控件验证状态关联,自定义
validate方法返回的错误无法传递到内部input - 错误状态判断逻辑错位,未同时结合控件
touched状态和invalid状态触发显示
修复步骤
1. 关联外部表单控件实例
在组件构造函数中注入当前自定义控件的NgControl实例,直接绑定值访问器,删除组件元数据中NG_VALUE_ACCESSOR、NG_VALIDATORS的provider配置:
import { Component, HostListener, Input, OnInit, Self, Optional } from '@angular/core'; import { NgControl } from '@angular/forms'; // 其他导入保持不变 @Component({ selector: 'app-ref-urlcheck', templateUrl: './ref-urlcheck.component.html', styleUrls: ['./ref-urlcheck.component.scss'], // 移除原有的providers配置 }) export class RefURLcheckComponent implements OnInit, ControlValueAccessor, MatFormFieldControl<any>, Validator { // 其他属性保持不变 constructor(@Self() @Optional() public ngControl: NgControl) { if (this.ngControl) { this.ngControl.valueAccessor = this; } } // 其他逻辑 }
2. 修正模板绑定
移除冗余的ngModel、模板引用变量,直接绑定外部控件的错误状态控制mat-error显示:
<mat-form-field> <input matInput type="url" [maxlength]="maxLen" [errorStateMatcher]="errorStateMatcher" (input)="changeInput($any($event.target).value)" [disabled]="isDisabled" [value]="strUrl" placeholder="Homepage" /> <mat-error *ngIf="ngControl.invalid && ngControl.touched"> {{ ngControl.hasError('tooShort') ? '链接长度不能小于5位' : '输入格式不符合要求' }} </mat-error> </mat-form-field>
3. 正确实现MatFormFieldControl接口
初始化必要属性,在所有状态变更节点触发状态更新通知:
import { Subject } from 'rxjs'; export class RefURLcheckComponent implements OnInit, ControlValueAccessor, MatFormFieldControl<any>, Validator { @Input() maxLen = 254; strUrl: string = ''; onChange = (changedUrl) => { }; onTouched = () => { }; isDisabled = false; touched = false; @HostListener('focusin', ['$event.target.value']) onFocusIn; // 正确初始化MatFormFieldControl要求的属性 stateChanges = new Subject<void>(); focused = false; controlType = 'ref-urlcheck'; get empty() { return !this.strUrl?.trim(); } get shouldLabelFloat() { return this.focused || !this.empty; } id = `ref-urlcheck-${Math.random().toString(36).slice(2, 10)}`; userAriaDescribedBy?: string; autofilled?: boolean; required: boolean; constructor(@Self() @Optional() public ngControl: NgControl) { if (this.ngControl) { this.ngControl.valueAccessor = this; } } // 实现空逻辑即可,无需抛错 onContainerClick(event: MouseEvent): void {} setDescribedByIds(ids: string[]): void { this.userAriaDescribedBy = ids.join(' '); } ngOnInit(): void { this.onFocusIn = (inputVal) => { this.focused = true; this.stateChanges.next(); this.markAsTouched(); }; // 监听失焦事件更新状态 document.addEventListener('focusout', () => { this.focused = false; this.stateChanges.next(); }); } setDisabledState?(isDisabled: boolean): void { this.isDisabled = isDisabled; this.stateChanges.next(); } registerOnTouched(onTouched: () => {}): void { this.onTouched = onTouched; } registerOnChange(onChange: (changedValue: string) => {}): void { this.onChange = onChange; } writeValue(value: string): void { this.strUrl = value || ''; this.stateChanges.next(); } markAsTouched() { if (!this.touched) { this.onTouched(); this.touched = true; this.stateChanges.next(); } } changeInput(inVal: string) { this.strUrl = inVal; this.onChange(inVal); this.stateChanges.next(); } readonly errorStateMatcher: ErrorStateMatcher = { isErrorState: () => { return this.ngControl?.invalid && this.touched; } }; validate(control: AbstractControl): ValidationErrors | null { const val = control.value?.trim() || ''; if (val.length > 0 && val.length <= 5) { return { tooShort: true }; } return null; } // 组件销毁时关闭subject ngOnDestroy() { this.stateChanges.complete(); } }
修改完成后,当输入框失焦且内容不符合验证规则时,mat-error就会正常显示。
内容的提问来源于stack exchange,提问作者LeO
相关产品推荐
相关产品推荐

