You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 23:45:03