Angular Material 自定义表单域不显示 mat-error

Angular Material custom form field doesn't show mat-error

我以为我几乎 但不知何故控制欺骗了我 :-/

<form [formGroup]="form">
    <app-ref-urlcheck [maxLen]="20" formControlName="url"></app-ref-urlcheck>
</form>

模板看起来像

<mat-form-field>
  <input matInput #inUrl="ngModel" [(ngModel)]="value" type="url" [attr.maxlength]="maxLen" [errorStateMatcher]="errorStateMatcher"
    (input)="changeInput(inUrl.value)" [disabled]="isDisabled" [value]="strUrl" 
    placeholder="Homepage" />
  <mat-error>test error</mat-error> <!-- doesn't show up - neither the next -->
  <mat-error *ngIf="(inUrl.touched && inUrl.invalid)">This field is required</mat-error>
</mat-form-field>

及主要内容

import { Component, HostListener, Input, OnInit } from '@angular/core';
import { AbstractControl, ControlValueAccessor, FormControl, NgControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';
import { MatFormFieldControl } from '@angular/material/form-field';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-ref-urlcheck',
  templateUrl: './ref-urlcheck.component.html',
  styleUrls: ['./ref-urlcheck.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: RefURLcheckComponent
    },
    {
      provide: NG_VALIDATORS,
      multi: true,
      useExisting: RefURLcheckComponent
    }
  ]
})
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;

  constructor() { }
  onContainerClick(event: MouseEvent): void {
    throw new Error('Method not implemented.');
  }
  setDescribedByIds(ids: string[]): void {
    throw new Error('Method not implemented.');
  }
  userAriaDescribedBy?: string;
  autofilled?: boolean;
  controlType?: string;
  errorState: boolean;
  disabled: boolean;
  required: boolean;
  shouldLabelFloat: boolean;
  empty: boolean;
  focused: boolean;
  ngControl: NgControl;
  placeholder: string;
  id: string;
  stateChanges: Observable<void>;
  value: any;

  ngOnInit(): void {
  }

  setDisabledState?(isDisabled: boolean): void {
    this.isDisabled = isDisabled;
  }
  registerOnTouched(onTouched: () => {}): void {
    this.onTouched = onTouched;
  }
  registerOnChange(onChange: (changedValue: string) => {}): void {
    this.onChange = onChange;

    this.onFocusIn = (inputVal) => {
      console.log('focus in', inputVal);
      this.markAsTouched();
    };


  }

  writeValue(value: string): void {
    this.strUrl = value;
  }

  markAsTouched() {
    if (!this.touched) {
      this.onTouched();
      this.touched = true;
    }
  }

  changeInput(inVal: string) {
    this.onChange(inVal);
    this.markAsTouched();
  }



  readonly errorStateMatcher: ErrorStateMatcher = {
    isErrorState: (ctrl: FormControl) => {
      console.log('errorStateMatch...')
      this.errorState = true;
      return (ctrl && ctrl.invalid);
    }
  };

  validate(control: AbstractControl): ValidationErrors | null {
    if (control?.value.length <= 5) {
      this.errorState = true;
      return {
        tooShort: true
      };
    }
    this.errorState = false;
    return null;
  }
}

与参考示例中的问题相同:如何显示 <mat-error>?它甚至没有出现。

重新使用了附加的代码,怀疑 FormControl 在验证失败时没有更新错误。

当验证失败时,应将错误设置为FormControl,如下所示:

this.inUrl.control.setErrors({ tooShort: true });
import { ViewChild } from '@angular/core';

export class RefURLcheckComponent
  implements OnInit, ControlValueAccessor, MatFormFieldControl<any>, Validator
{
  @ViewChild('inUrl', { static: true }) inUrl: NgControl;

  ...

  validate(control: AbstractControl): ValidationErrors | null {
    if (control?.value?.length <= 5) {
      this.errorState = true;
      this.inUrl.control.setErrors({ tooShort: true });
      return {
        tooShort: true,
      };
    }

    this.errorState = false;
    this.inUrl.control.setErrors(null);
    return null;
  }
}

Sample Demo on StackBlitz

感谢 @Yong Shun 我知道了如何正确地进行管理。似乎需要使用常规模板驱动方法(用于 input 字段的更新)包装 input 字段并将此组件用作反应组件。所以我的自定义控件内部有一个处理状态的控件。

我从我的原始代码中删除了所有不必要的东西并包含了一些来自 guideline 的小提示。

这是我的(简化版)working example - 以备有人再次需要时使用。