我使用带有角度材质的 angular 8 来创建我的应用程序。
我定义了以下表单字段:
<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
是我使用以下代码创建的组件:
该网页:
<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>
更新我看到您将垫子错误应用于表单控件"fromDate"。所以验证器必须应用于 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更改此行为,但这不是计划的问题。