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 更改此行为,但这不是计划中的问题
我使用 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 更改此行为,但这不是计划中的问题