mat-error 在我创建的组件上不可见

mat-error is not visible on a component that I created

我使用 angular 8 和 angular material 创建我的应用程序。

我定义了以下表单域:

<mat-form-field floatLabel="always">
        <app-my-datetime-input placeholder="From" formControlName="fromDatetime"></app-my-datetime-input>
        <mat-error>{{getError('fromDatetime')}}hello</mat-error>
        <mat-hint>YYYY-MM-DD HH:MM:SS</mat-hint>
      </mat-form-field>
  

app-my-datetime-input 是我使用以下代码创建的组件:

html:

<div [formGroup]="parts">
  <input  matInput mask="0000-00-00 00:00:00" formControlName="datetime" (input)="_handleInput()" />
</div>

这是打字稿:

import {Component, ElementRef, forwardRef, HostBinding, Input, OnDestroy, OnInit, Optional, Self, ViewChild} from '@angular/core';
import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, NgControl} from '@angular/forms';
import {MatFormFieldControl, MatInput} from '@angular/material';
import {Subject} from 'rxjs';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {FocusMonitor} from '@angular/cdk/a11y';

@Component({
  selector: 'app-my-datetime-input',
  templateUrl: './my-datetime-input.component.html',
  styleUrls: ['./my-datetime-input.component.scss'],
  providers: [{provide: MatFormFieldControl, useExisting: MyDatetimeInputComponent}],
})

export class MyDatetimeInputComponent implements ControlValueAccessor, MatFormFieldControl<string>,
              OnDestroy {

  get empty() {
    const {value: {datetime}} = this.parts;

    return !datetime;
  }
  // TODO: fix should label float
  get shouldLabelFloat() { return this.focused || !this.empty; }

  @Input()
  get placeholder(): string { return this._placeholder; }
  set placeholder(value: string) {
    this._placeholder = value;
    this.stateChanges.next();
  }

  @Input()
  get required(): boolean { return this._required; }
  set required(value: boolean) {
    this._required = coerceBooleanProperty(value);
    this.stateChanges.next();
  }

  @Input()
  get disabled(): boolean { return this._disabled; }
  set disabled(value: boolean) {
    this._disabled = coerceBooleanProperty(value);
    this._disabled ? this.parts.disable() : this.parts.enable();
    this.stateChanges.next();
  }

  @Input()
  get value(): string {
    const {value: {datetime}} = this.parts;
    return datetime;
  }
  set value(datetime: string) {
    this.parts.setValue({datetime});
    this.stateChanges.next();
  }

  constructor(
    formBuilder: FormBuilder,
    // tslint:disable-next-line:variable-name
    private _focusMonitor: FocusMonitor,
    // tslint:disable-next-line:variable-name
    private _elementRef: ElementRef<HTMLElement>,
    @Optional() @Self() public ngControl: NgControl) {

    this.parts = formBuilder.group({
      datetime: '',
    });

    _focusMonitor.monitor(_elementRef, true).subscribe(origin => {
      if (this.focused && !origin) {
        this.onTouched();
      }
      this.focused = !!origin;
      this.stateChanges.next();
    });

    if (this.ngControl != null) {
      this.ngControl.valueAccessor = this;
    }
  }
  static nextId = 0;

  parts: FormGroup;
  stateChanges = new Subject<void>();
  focused = false;
  errorState = false;
  controlType = 'my-datetime-input';
  id = `my-datetime-input-${MyDatetimeInputComponent.nextId++}`;
  describedBy = '';
  // tslint:disable-next-line:variable-name
  private _placeholder: string;
  // tslint:disable-next-line:variable-name
  private _required = false;
  // tslint:disable-next-line:variable-name
  private _disabled = false;
  onChange = (_: any) => {};
  onTouched = () => {};

  ngOnDestroy() {
    this.stateChanges.complete();
    this._focusMonitor.stopMonitoring(this._elementRef);
  }

  setDescribedByIds(ids: string[]) {
    this.describedBy = ids.join(' ');
  }

  onContainerClick(event: MouseEvent) {
    if ((event.target as Element).tagName.toLowerCase() !== 'input') {
      // tslint:disable-next-line:no-non-null-assertion
      this._elementRef.nativeElement.querySelector('input')!.focus();
    }
  }

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

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  _handleInput(): void {
    this.onChange(this.parts.value.datetime);
  }

}

这是我第一次创建自己的表单字段组件,所以我可能在那里做错了.. mat-error 不可见。如您所见,我将单词 hello 附加到 mat-error 的末尾,但我仍然没有看到它显示出来。所以我猜我应该实施 MatFormFieldControl 是一种 .. 错误更少的方式?! :) 所以我真的不知道我做错了什么,所以非常感谢有关此问题的任何信息。

谢谢

更新

添加了 (blur)="onTouched() 但不幸的是结果是一样的。

我有一个表单验证,可确保起始日期不更新于截止日期。这是我的验证函数:

 static fromToRangeValidator(): ValidatorFn {
      return (group: FormGroup): ValidationErrors => {
        const fromDate = group.get('fromDatetime');
        const toDate = group.get('toDatetime');
        if (fromDate.value !== '' && toDate.value !== '') {
          const fromMoment = moment(fromDate.value, 'YYYYMMDDHHmmss');
          const toMoment = moment(toDate.value, 'YYYYMMDDHHmmss');
          if (toMoment.isBefore(fromMoment)) {
            fromDate.setErrors({greaterThen: true});
            toDate.setErrors({lessThen: true});
          }
        }
        return;
      };
    }

表单因为报错没有提交但是报错还是没有显示

只有当控件被触摸时才会显示错误(*),所以当发生某些事情时你需要说你的控件被触摸了。不要错过,您在自定义表单控件中的输入会被触及,但不会触及自定义表单控件本身。

你可以使用(模糊)

<div [formGroup]="parts">
  <input matInput mask="0000-00-00 00:00:00" 
      formControlName="datetime" 
      (blur)="onTouched()"
      (input)="_handleInput()" />
</div>

Update 我看到您将 mat-error 应用于 FormControl "fromDate"。所以validator必须应用于formControl,而不是FormGroup -否则是无效的formGroup -

自定义验证器必须是

fromToRangeValidator(): ValidatorFn {
      //see that the argument is a FormControl
      return (control: FormControl): ValidationErrors => {
        //the formGroup is control.parent
        const group=control.parent;
        //but we must sure that is defined
        if (!group) return null;
        const fromDate = group.get('fromDatetime');
        const toDate = group.get('toDatetime');
        //...rest of your code...
        if (fromDate.value !== '' && toDate.value !== '') {
          const fromMoment = moment(fromDate.value, 'YYYYMMDDHHmmss');
          const toMoment = moment(toDate.value, 'YYYYMMDDHHmmss');
          if (toMoment.isBefore(fromMoment)) {
            fromDate.setErrors({greaterThen: true});
            toDate.setErrors({lessThen: true});
          }
        }
        return;
      };
    }
}

并且,当您创建表单时,您将验证程序应用于控件

form=new FormGroup({
    fromDatetime:new FormControl('',this.fromToRangeValidator()),
    toDatetime:new FormControl()
  })

(*)您确实可以使用自定义 ErrorStateMatcher 更改此行为,但这不是计划中的问题