angularマテリアルの問題について話をしてきました。実際、それは問題だと思いますが、最初に誤解を探すことを好みます。
私の問題に関する最初のことはコンテキストです。私は2つの入力を含む単純なフォームを実行しようとしています:パスワードとその確認。
user-form.component.ts
this.newUserForm = this.fb.group({
type: ['', Validators.required],
firstname: ['', Validators.required],
lastname: ['', Validators.required],
login: ['', Validators.required],
matchingPasswordsForm: this.fb.group(
{
password1: ['', Validators.required],
password2: ['', Validators.required],
},
{
validator: MatchingPasswordValidator.validate,
},
),
mail: ['', [Validators.required, Validators.pattern(EMAIL_PATTERN)]],
cbaNumber: [
'411000000',
[Validators.required, Validators.pattern(CBANUMBER_PATTERN)],
],
phone: ['', [Validators.required, Validators.pattern(PHONE_PATTERN)]],
}
私の関心は、一致するPasswordsForm FormGroupです。検証ツールが表示されます。
ここでバリデーター:
matching-password.validator.ts
export class MatchingPasswordValidator {
constructor() {}
static validate(c: FormGroup): ValidationErrors | null {
if (c.get('password2').value !== c.get('password1').value) {
return { matchingPassword: true};
}
return null;
}
}
およびHTML。
user-form.component.html
<div class="row" formGroupName="matchingPasswordsForm">
<mat-form-field class="col-md-6 col-sm-12">
<input matInput placeholder="Mot de passe:" formControlName="password1">
<mat-error ngxErrors="matchingPasswordsForm.password1">
<p ngxError="required" [when]="['dirty', 'touched']">{{requiredMessage}}</p>
</mat-error>
</mat-form-field>
<mat-form-field class="col-md-6 col-sm-12">
<input matInput placeholder="Confirmez" formControlName="password2">
<mat-error ngxErrors="matchingPasswordsForm.password2">
<p ngxError="required" [when]="['dirty', 'touched']">{{requiredMessage}}</p>
</mat-error>
<!-- -->
<!-- problem is here -->
<!-- -->
<mat-error ngxErrors="matchingPasswordsForm" class="mat-error">
<p ngxError="matchingPassword" [when]="['dirty', 'touched']">{{passwordMatchErrorMessage}}</p>
</mat-error>
<!-- ^^^^^^^^^^^^^^^^ -->
<!-- /problem is here -->
<!-- -->
</mat-form-field>
</div>
興味深いコードをコメントで囲みました。
さて、いくつかの説明:タグで、password2がタッチされると、私のエラーが表示されます:
しかし、間違ったパスワードを書くと、エラーはもう表示されません:
最初に、カスタムバリデータの使用を誤解していると思いました。しかし、私が全部を置き換えると完璧に機能します!
エラーをヒントで置き換える
<mat-hint ngxErrors="matchinghPasswordsForm">
<p ngxError="matchingPassword" [when]="['dirty', 'touched']">{{passwordMatchErrorMessage}}</p>
</mat-hint>
マテリアルデザインgithubに問題を投稿する前に、私が明確であったことを願っています。
私が何かを誤解した場合は、私が逃したものに火をつけてください。
最後に、私のテストはngxerrorsと* ngifで行われました。コードサンプルを読みやすくするには、ngxerrorsのみを使用します。
あなたがかかる時間を事前に感謝します。
Alex は正しいです。 ErrorStateMatcherを使用する必要があります。私はこれを理解するために多くの研究をしなければなりませんでした、そして、私に全体の答えを与えた単一のソースがありませんでした。この問題に対する独自のソリューションを作成するために、複数のソースから学んだ情報をまとめなければなりませんでした。次の例が、私が経験した頭痛からあなたを救うことを願っています。
ユーザー登録ページにAngular Material要素を使用するフォームの例を次に示します。
<form [formGroup]="userRegistrationForm" novalidate>
<mat-form-field>
<input matInput placeholder="Full name" type="text" formControlName="fullName">
<mat-error>
{{errors.fullName}}
</mat-error>
</mat-form-field>
<div formGroupName="emailGroup">
<mat-form-field>
<input matInput placeholder="Email address" type="email" formControlName="email">
<mat-error>
{{errors.email}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Confirm email address" type="email" formControlName="confirmEmail" [errorStateMatcher]="confirmValidParentMatcher">
<mat-error>
{{errors.confirmEmail}}
</mat-error>
</mat-form-field>
</div>
<div formGroupName="passwordGroup">
<mat-form-field>
<input matInput placeholder="Password" type="password" formControlName="password">
<mat-error>
{{errors.password}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Confirm password" type="password" formControlName="confirmPassword" [errorStateMatcher]="confirmValidParentMatcher">
<mat-error>
{{errors.confirmPassword}}
</mat-error>
</mat-form-field>
</div>
<button mat-raised-button [disabled]="userRegistrationForm.invalid" (click)="register()">Register</button>
</form>
ご覧のとおり、Angular Materialの<mat-form-field>
、<input matInput>
、および<mat-error>
タグを使用しています。最初に考えたのは、*ngIf
セクションを表示するタイミングを制御する<mat-error>
ディレクティブを追加することでしたが、これは効果がありません!可視性は、実際に<mat-form-field>
の有効性(および「タッチ済み」ステータス)によって制御され、HTMLまたはAngularの別のフォームフィールドとの同等性をテストするための検証機能は提供されていません。これが、確認フィールドのerrorStateMatcher
ディレクティブが作用する場所です。
errorStateMatcher
ディレクティブはAngular Materialに組み込まれ、カスタムメソッドを使用して<mat-form-field>
フォームコントロールの有効性を判断し、有効性ステータスにアクセスできるようにします。そうする親の。このユースケースでerrorStateMatcherを使用する方法を理解するために、まずコンポーネントクラスを見てみましょう。
FormBuilderを使用してフォームの検証を設定するAngular Componentクラスを次に示します。
export class App {
userRegistrationForm: FormGroup;
confirmValidParentMatcher = new ConfirmValidParentMatcher();
errors = errorMessages;
constructor(
private formBuilder: FormBuilder
) {
this.createForm();
}
createForm() {
this.userRegistrationForm = this.formBuilder.group({
fullName: ['', [
Validators.required,
Validators.minLength(1),
Validators.maxLength(128)
]],
emailGroup: this.formBuilder.group({
email: ['', [
Validators.required,
Validators.email
]],
confirmEmail: ['', Validators.required]
}, { validator: CustomValidators.childrenEqual}),
passwordGroup: this.formBuilder.group({
password: ['', [
Validators.required,
Validators.pattern(regExps.password)
]],
confirmPassword: ['', Validators.required]
}, { validator: CustomValidators.childrenEqual})
});
}
register(): void {
// API call to register your user
}
}
このクラスは、ユーザー登録フォームのFormBuilder
をセットアップします。クラスには2つのFormGroup
があることに注意してください。1つは電子メールアドレスの確認用、もう1つはパスワードの確認用です。個々のフィールドは適切なバリデーター関数を使用しますが、両方ともグループレベルでカスタムバリデーターを使用し、各グループのフィールドが互いに等しいことを確認し、等しくない場合は検証エラーを返します。
グループのカスタムバリデーターとerrorStateMatcherディレクティブの組み合わせは、確認フィールドの検証エラーを適切に表示するために必要な完全な機能を提供します。カスタム検証モジュールを見てみましょう。
簡単に再利用できるように、カスタム検証機能を独自のモジュールに分割することにしました。同じ理由で、フォーム検証に関連する他のもの、つまり正規表現とエラーメッセージをそのモジュールに配置することも選択しました。少し先を考えると、ユーザーがユーザー更新フォームで自分のメールアドレスとパスワードを変更できるようにする可能性がありますよね?モジュール全体のコードは次のとおりです。
import { FormGroup, FormControl, FormGroupDirective, NgForm, ValidatorFn } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material';
/**
* Custom validator functions for reactive form validation
*/
export class CustomValidators {
/**
* Validates that child controls in the form group are equal
*/
static childrenEqual: ValidatorFn = (formGroup: FormGroup) => {
const [firstControlName, ...otherControlNames] = Object.keys(formGroup.controls || {});
const isValid = otherControlNames.every(controlName => formGroup.get(controlName).value === formGroup.get(firstControlName).value);
return isValid ? null : { childrenNotEqual: true };
}
}
/**
* Custom ErrorStateMatcher which returns true (error exists) when the parent form group is invalid and the control has been touched
*/
export class ConfirmValidParentMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
return control.parent.invalid && control.touched;
}
}
/**
* Collection of reusable RegExps
*/
export const regExps: { [key: string]: RegExp } = {
password: /^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{7,15}$/
};
/**
* Collection of reusable error messages
*/
export const errorMessages: { [key: string]: string } = {
fullName: 'Full name must be between 1 and 128 characters',
email: 'Email must be a valid email address (username@domain)',
confirmEmail: 'Email addresses must match',
password: 'Password must be between 7 and 15 characters, and contain at least one number and special character',
confirmPassword: 'Passwords must match'
};
まず、グループのカスタム検証関数CustomValidators.childrenEqual()
を見てみましょう。私はオブジェクト指向プログラミングのバックグラウンドから来たので、この関数を静的なクラスメソッドにすることを選択しましたが、スタンドアロン関数にすることもできます。この関数はValidatorFn
型(または適切なリテラルシグネチャ)であり、AbstractControl
型の単一のパラメーター、または任意の派生型を取る必要があります。私はそれをFormGroup
にすることを選択しました。なぜなら、それがその用途であるからです。
関数のコードは、FormGroup
内のすべてのコントロールを反復処理し、それらの値がすべて最初のコントロールの値と等しくなるようにします。存在する場合、null
(エラーがないことを示す)を返し、そうでない場合はchildrenNotEqual
エラーを返します。
そのため、フィールドが等しくない場合、グループのステータスは無効になりますが、そのステータスを使用して、エラーメッセージを表示するタイミングを制御する必要があります。 ErrorStateMatcher ConfirmValidParentMatcher
は、これを可能にするものです。 errorStateMatcherディレクティブでは、Angular Materialで提供されたErrorStateMatcherクラスを実装するクラスのインスタンスをポイントする必要があります。これがここで使用される署名です。 ErrorStateMatcherには、isErrorState
メソッドの実装と、コードに示されている署名が必要です。 true
またはfalse
;を返します。 true
は、エラーが存在することを示し、入力要素のステータスが無効になります。
このメソッドの1行のコードは非常に単純です。親コントロール(FormGroup)が無効な場合は、フィールドがタッチされた場合にのみ、true
(エラーが存在する)を返します。これは、フォーム上の残りのフィールドに使用している<mat-error>
のデフォルトの動作と一致します。
すべてをまとめるために、フィールドが等しくないときにエラーを返すカスタムバリデータと、グループが無効なときに表示される<mat-error>
を持つFormGroupがあります。この機能の実際の動作を確認するために、前述のコードの実装で動作する plunker を示します。
また、このソリューションをブログに掲載しました here 。
カスタム検証を作成する方法:
コンポーネントの内部プロパティ 'isValid'がfalseの場合、入力ステータスをエラーに設定し、メッセージを表示します。
HTML:
<input matInput [formControl]="inputControl" [placeholder]="placeholder" [readonly]="readonly" [errorStateMatcher]="matcher"> <mat-error *ngIf="!isValid"> Input not valid. </mat-error>
TS:
isValid = true; changeValitationStatus() { this.matcher = new InputErrorStateMatcher(!this.isValid); } matcher = new InputErrorStateMatcher(!this.isValid); class InputErrorStateMatcher implements ErrorStateMatcher { constructor(private errorstate: boolean) {} isErrorState(control: FormControl|null, form: FormGroupDirective|NgForm|null):boolean { return this.errorstate; } }
このようにして、formControlのみを使用して検証を行います。
obsessiveprogrammer の答えは私にとっては正しかったが、angular 6とchildrenEqual
(推奨オプションであるstrictNullChecks
関数を変更する必要があったangularチーム)これ:
static childrenEqual: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
const f = control as FormGroup;
const [firstControlName, ...otherControlNames] = Object.keys(f.controls || {});
if(f.get(firstControlName) == null) {
return null;
}
otherControlNames.forEach(controlName => {
if(f.get(controlName) == null) {
return null;
}
})
const isValid = otherControlNames.every(controlName => f.get(controlName)!.value === f.get(firstControlName)!.value);
return isValid ? null : { childrenNotEqual: true };
}