最新のAngularと最新のAngularマテリアルを使用しています。日付ピッカーがあり、検証を追加したいと考えています。ドキュメントではrequired
属性はそのまま使用できますが、他のフォーム要素と同じようにエラーを処理するようには見えません。
ここに私のマークアップがあります:
<mat-form-field class="full-width">
<input matInput [matDatepicker]="dob" placeholder="Date of birth" [(ngModel)]="myService.request.dob" #dob="ngModel" required app-validateAdult>
<mat-datepicker-toggle matSuffix [for]="dob"></mat-datepicker-toggle>
<mat-datepicker #dob></mat-datepicker>
<mat-error *ngIf="dob.errors && dob.errors.required">Your date of birth is required</mat-error>
</mat-form-field>
これは幸せなパスで機能するため、日付が選択されると、日付はmyService
の期待されるプロパティになります。
ただし、検証は期待どおりには機能しません。この場合、日付を入力せずにフィールドをクリックしてからフィールドから出ると、入力doesは赤のスタイリングを取得しますが、通常の[controlName].errors
オブジェクトは入力されません。これは、通常の方法(同じページの日付ピッカーではない他の入力で機能する方法)でエラーメッセージを表示しても機能しないことを意味します。
<mat-error *ngIf="dob.errors && dob.errors.required">Your date of birth is required</mat-error>
日付ピッカーは*ngIf
を更新しないため、dob.errors
は決して真ではありません。そのため、入力が無効としてスタイル設定されている場合でも、エラーメッセージは表示されません。
これは正しいですか?私は何かを見逃しましたか?
また、datepickerで選択された日付がユーザーが18歳以上であることを示すことを検証するカスタムディレクティブを追加しようとしました。
export class AdultValidator implements Validator {
constructor(
@Attribute('app-validateAdult') public validateAdult: string
) { }
validate(control: AbstractControl): { [key: string]: any } {
const dob = control.value;
const today = moment().startOf('day');
const delta = today.diff(dob, 'years', false);
if (delta <= 18) {
return {
validateAdult: {
'requiredAge': '18+',
'currentAge': delta
}
};
}
return null;
}
}
この場合、適切なときにエラーを表示するために、同様のmatError
(代わりにdob.errors.validateAdult
にリンクされていることを除く)を使用しようとしています。
これの興味深い点は、18年未満前の日付を選択した場合、入力、ラベルなど全体がデフォルトの赤いエラースタイリングを取得するため、何かが起こっているが、それでもエラーメッセージが表示されないことです。
どんな提案でも大歓迎です!
正確なバージョン:
Angular CLI: 1.6.3
Node: 6.11.0
OS: win32 x64
Angular: 5.1.3
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router
@angular/cdk: 5.0.4
@angular/cli: 1.6.3
@angular/flex-layout: 2.0.0-beta.12
@angular/material-moment-adapter: 5.0.4
@angular/material: 5.0.4
@angular-devkit/build-optimizer: 0.0.36
@angular-devkit/core: 0.0.22
@angular-devkit/schematics: 0.0.42
@ngtools/json-schema: 1.1.0
@ngtools/webpack: 1.9.3
@schematics/angular: 0.1.11
@schematics/schematics: 0.0.11
TypeScript: 2.4.2
webpack: 3.10.0
私はErrorStateMatcher
を使用せずにこれを機能させることができましたが、それは解決策に到達するのに役立ちました。将来の参考のために、または他の人を助けるためにここを離れます.
フォームをテンプレート駆動型フォームではなくリアクティブフォームに変換し、カスタムバリデーターディレクティブをより単純なバリデーター(非ディレクティブベース)に変更しました。
作業コードは次のとおりです。
my-form.component.html:
<div class="container" fxlayoutgap="16px" fxlayout fxlayout.xs="column" fxlayout.sm="column" *ngIf="fieldset.controls[control].type === 'datepicker'">
<mat-form-field class="full-width" fxflex>
<input matInput
[formControlName]="control"
[matDatepicker]="dob"
[placeholder]="fieldset.controls[control].label"
[max]="fieldset.controls[control].validation.max">
<mat-datepicker-toggle matSuffix [for]="dob"></mat-datepicker-toggle>
<mat-datepicker #dob></mat-datepicker>
<mat-error *ngIf="myForm.get(control).hasError('required')">
{{fieldset.controls[control].validationMessages.required}}</mat-error>
<mat-error *ngIf="myForm.get(control).hasError('underEighteen')">
{{fieldset.controls[control].validationMessages.underEighteen}}
</mat-error>
</mat-form-field>
</div>
注:上記のコードは、ngFor
とfieldset
の値を定義するネストされたcontrol
ループの内部にあります。この例では、control
は文字列dob
にマップされます。
over-eighteen.validator.ts:
import { ValidatorFn, AbstractControl } from '@angular/forms';
import * as moment from 'moment';
export function overEighteen(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } => {
const dob = control.value;
const today = moment().startOf('day');
const delta = today.diff(dob, 'years', false);
if (delta <= 18) {
return {
underEighteen: {
'requiredAge': '18+',
'currentAge': delta
}
};
}
return null;
};
}
my-form.component.ts:
buildForm(): void {
const formObject = {};
this.myService.request.fieldsets.forEach((controlsGroup, index) => {
this.fieldsets.Push({
controlNames: Object.keys(controlsGroup.controls)
});
for (const control in controlsGroup.controls) {
if (controlsGroup.controls.hasOwnProperty(control)) {
const controlData = controlsGroup.controls[control];
const controlAttributes = [controlData.value];
const validators = [];
if (controlData.validation) {
for (const validator in controlData.validation) {
if (controlData.validation.hasOwnProperty(validator)) {
if (validator === 'overEighteenValidator') {
validators.Push(this.overEighteenValidator);
} else {
validators.Push(Validators[validator]);
}
}
}
controlAttributes.Push(Validators.compose(validators));
}
formObject[control] = controlAttributes;
}
}
});
this.myForm = this.fb.group(formObject);
}
Angular Material FormsでErrorStateMatcherを使用します。完全に機能します。
次のようなコードが必要です。
<mat-form-field class="full-width">
<input matInput [matDatepicker]="dob" placeholder="Date of birth" formControlName="dob" required app-validateAdult>
<mat-datepicker-toggle matSuffix [for]="dob"></mat-datepicker-toggle>
<mat-datepicker #dob></mat-datepicker>
<mat-error *ngIf="dob.hasError('required')">Your date of birth is required</mat-error>
</mat-form-field>
そしてTypeScript:
import { ErrorStateMatcher } from '@angular/material/core';
export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(
control: FormControl | null,
form: FormGroupDirective | NgForm | null
): boolean {
const isSubmitted = form && form.submitted;
return !!(
control &&
control.invalid &&
(control.dirty || control.touched || isSubmitted)
);
}
}
export class AdultValidator implements Validator {
dob = new FormControl('', [
Validators.required
]);
matcher = new MyErrorStateMatcher();
}
詳細については、こちらをご覧ください: https://material.angular.io/components/input/overview
これをビューページに追加します。
<mat-form-field>
<input matInput [matDatepicker]="dp" placeholder="Employement date" >
<mat-datepicker-toggle matSuffix [for]="dp"></mat-datepicker-toggle>
<mat-datepicker #dp></mat-datepicker>
</mat-form-field>
モジュールにMatDatepickerModule、MatNativeDateModuleをインポートするだけです
入力参照の名前は以下のように変更できます。入力要素#dobInputはmat-errorでのみ参照されることに注意してください。
<mat-form-field class="full-width">
<input matInput [matDatepicker]="dob" placeholder="Date of birth" [(ngModel)]="myService.request.dob" #dobInput="ngModel" required app-validateAdult>
<mat-datepicker-toggle matSuffix [for]="dob"></mat-datepicker-toggle>
<mat-datepicker #dob></mat-datepicker>
<mat-error *ngIf="dobInput.errors && dobInput.errors.required">Your date of birth is required</mat-error>
ピッカーは#dboによって参照されます
[matDatepicker]="dob"
<mat-datepicker-toggle matSuffix [for]="dob"></mat-datepicker-toggle>
#dobが重複しています。これはangular検証で望ましくない動作をする可能性があります。
あなたが持っている
<input #dob='ngModel'
そして
<mat-datepicker #dob></mat-datepicker>
命名規則を修正して、何が起こるかを確認してください。
以下のように* ngIfをカスタム検証に設定しようとしましたか?
<mat-error *ngIf="dob.errors && dob.errors.validateAdult">Your date of birth
is less than 18 ?</mat-error>
それが機能する場合、別のバリデータを作成して、必要なネイティブ検証動作をシミュレートできます。
export class CustomRequireValidator implements Validator {
constructor(
@Attribute('app-validateRequired') public validateRequired: string
) { }
validate(control: AbstractControl): { [key: string]: any } {
let value = control.value;
if (!value || value == null || value.toString().length == 0) {
return requireValidator: {
'requiredAge': 'This field is required',
}
}
return null;
}
}
そして、以下のように前のngIfを使用します:
<mat-error *ngIf="dob.errors && dob.errors.requireValidator">Your date of
birth is less than 18 ?</mat-error>
私はそれをテストしませんでしたが、論理的には動作するはずです。