私はリアクティブangularフォームを作成していて、送信時にすべてのバリデーターをトリガーする方法を見つけようとしています。バリデーターが同期のものである場合は、それ以外の場合、バリデーターが非同期のものであり、まだトリガーされていない場合、ngSubmit
メソッドのフォームは保留中ステータスになります。フォームのstatusChange
プロパティのサブスクライブを登録しようとしましたが、そうではありませんmarkAsTouched
関数を使用して手動で検証を呼び出すとトリガーされます。
ここにいくつかのスニペットがあります:
//initialization of form and watching for statusChanges
ngOnInit() {
this.ctrlForm = new FormGroup({
'nome': new FormControl('', Validators.required),
'razao_social': new FormControl('', [], CustomValidators.uniqueName),
'cnpj': new FormControl('', CustomValidators.cnpj),
});
this.ctrlForm.statusChanges.subscribe(
x => console.log('Observer got a next value: ' + x),
err => console.error('Observer got an error: ' + err),
() => console.log('Observer got a complete notification')
)
}
//called on ngSubmit
register(ctrlForm: NgForm) {
Forms.validateAllFormFields(this.ctrlForm);
console.log(ctrlForm.pending);
//above will be true if the async validator
//CustomValidators.uniqueName was not called during form fill.
}
//iterates on controls and call markAsTouched for validation,
//which doesn't fire statusChanges
validateAllFormFields(formGroup: FormGroup) {
Object.keys(formGroup.controls).forEach(field => {
const control = formGroup.get(field);
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
} else if (control instanceof FormGroup) {
this.validateAllFormFields(control);
}
});
}
非同期バリデーターが確実に実行され、すべてのバリデーターがトリガーされて完了した状態でレジスターロジックを続行できるようにするためのアイデアはありますか?
AngularはngSubmit
を実行する前に非同期バリデーターが完了するのを待ちません。そのため、バリデーターが解決されていない場合、フォームは無効になる可能性があります。
Subject
を使用してフォーム送信を発行すると、結果をswitchMap
から_form.statusChange
_にfilter
できます。
送信時にフォームが有効である場合は、startWith
から始めて、ハンギングエミッションが発生しないようにします。
PENDING
によるフィルタリングは、このステータスが変更されるのを待ち、take(1)
は、保留後の最初のエミッションでストリームが確実に完了するようにします:VALID
またはINVALID
。
_//
// <form (ngSubmit)="formSubmitSubject$.next()">
this.formSubmitSubject$ = new Subject();
this.formSubmitSubject$
.pipe(
tap(() => this.form.markAsDirty()),
switchMap(() =>
this.form.statusChanges.pipe(
startWith(this.form.status),
filter(status => status !== 'PENDING'),
take(1)
)
),
filter(status => status === 'VALID')
)
.subscribe(validationSuccessful => this.submitForm());
_
また、フォームをダーティとして設定する副作用を引き起こすtap
を追加することもできます。
すべてのコントロールの同期および非同期バリデーターを手動で呼び出し、すべての検証がパスしたかどうかを示すブール値を返す:
checkIfFormPassesValidation(formGroup: FormGroup) {
const syncValidationErrors = Object.keys(formGroup.controls).map(c => {
const control = formGroup.controls[c];
return !control.validator ? null : control.validator(control);
}).filter(errors => !!errors);
return combineLatest(Object.keys(formGroup.controls).map(c => {
const control = formGroup.controls[c];
return !control.asyncValidator ? of(null) : control.asyncValidator(control)
})).pipe(
map(asyncValidationErrors => {
const hasErrors = [...syncValidationErrors, ...asyncValidationErrors.filter(errors => !!errors)].length;
if (hasErrors) { // ensure errors display in UI...
Object.keys(formGroup.controls).forEach(key => {
formGroup.controls[key].markAsTouched();
formGroup.controls[key].updateValueAndValidity();
})
}
return !hasErrors;
})).toPromise();
}
使用法:
onSubmitForm() {
checkIfFormPassesValidation(this.formGroup)
.then(valid => {
if (valid) {
// proceed
}
});
}
markAsTouched
は検証を起動しません。代わりにmarkAsDirty
を使用すると、カスタムバリデーターが起動します。だから変更...
_control.markAsTouched({ onlySelf: true });
_
に
_ control.markAsDirty({ onlySelf: true });
_
また、v 5を使用している場合は、オプションの_updateOn: 'submit'
_を使用できます。これにより、フォームが送信されるまで値が更新されません(したがって検証されません)。そのために、次の変更を行います。
_this.ctrlForm = new FormGroup({
'nome': new FormControl('', Validators.required),
'razao_social': new FormControl('', [], CustomValidators.uniqueName),
'cnpj': new FormControl('', CustomValidators.cnpj),
}, { updateOn: 'submit' }); // add this!
_
これにより、もうthis.validateAllFormFields(control)
を呼び出す必要がないことを意味します。これは、ブールフラグを切り替えて検証などをチェックすると想定しています。
フォームのサンプルは次のとおりです。フォームを送信すると、常にエラーが返されます。
https://stackblitz.com/edit/angular-rjnfbv?file=app/app.component.ts
クラスform
でFormGroup
(リアクティブフォーム)を取得した場合、送信を続ける前に AbstractControl/Property/valid を使用してフォームが有効かどうかを確認しますそれをサーバーに。
私が使用する非同期バリデーターは、フォームフィールドが変更された後、=> Promise<ValidationErrors | null>
フォームが再び有効になる前にを返す必要があります。 Googleがこのように設計していなかったら変だと思います...しかし、彼らは設計しました!