我正在构建一个反应式角形式,我正试图找到一种方法来触发提交时的所有验证器。如果验证者是同步的,那就没关系,因为我可以获得内联的状态。否则,如果验证器是异步的并且尚未触发,则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);
}
});
}
关于如何确保执行异步验证器的任何想法,以便我可以继续使用触发并完成所有验证器的寄存器逻辑?
答案 0 :(得分:7)
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
来触发将表单设置为脏表单的副作用。
答案 1 :(得分:0)
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
答案 2 :(得分:0)
如果我得到了form
类的FormGroup
(反应形式),则在继续将其发送到服务器之前,我会使用AbstractControl/Property/valid检查该形式是否有效。 / p>
我使用的异步验证器必须在表单字段更改后必须返回=> Promise<ValidationErrors | null>
,在表单再次有效之前。 如果Google不像这样设计它,那会很奇怪...但是他们这样做了!
答案 3 :(得分:0)
我刚刚在我的应用中实现了此版本,该版本手动调用每个控件的同步和异步验证器,并返回一个布尔值,指示是否通过了所有验证:
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
}
});
}
答案 4 :(得分:0)
在本期中,也有一个解决方案作为指令以角度https://github.com/angular/angular/issues/31021
实现。答案 5 :(得分:0)
使用 formGroup.statusChanges 等待asyncValidators完成后再继续提交表单。如果asyncValidators没有错误,请继续提交。另一方面,如果失败,请不要提交。您的表格应该已经可以处理失败的验证器。如果您不再需要订阅,请记住取消订阅。
if (this.formGroup.pending) {
let sub = this.formGroup.statusChanges.subscribe((res) => {
if (this.formGroup.valid) {
this.submit();
}
sub.unsubscribe();
});
} else {
this.submit();
}