更新: 异步验证问题已成功解决。但是初始验证状态还有另一个问题。查看最新答案。
这是诀窍:
- 具有实现 ControlValueAccessor 接口的组件用作自定义控件。
- 该组件在某些反应式表单中用作 FormControl。
- 此自定义控件具有异步验证器。
问题:
值更改后立即调用来自 ControlValueAccessor 接口的方法 validate() 并且不等待异步验证器。当然,控制是无效的和未决的(因为正在进行验证),主窗体也将是无效的和未决的。一切正常。
但。当异步验证器完成验证并返回 null(表示值有效)时,自定义控件将有效并且状态也更改为有效,但父级仍然无效且处于挂起状态,因为来自值访问器的 validate() 尚未再次调用。
我试图从 validate() 方法返回 observable,但主窗体将其解释为错误对象。
我找到了解决方法:当异步验证器完成验证时,从自定义控件传播更改事件。它迫使主窗体再次调用 validate() 方法并获得正确的有效状态。但它看起来又脏又糙。
问题是: 必须做什么才能使父表单由来自子自定义控件的异步验证器管理?必须说它与同步验证器配合得很好。
所有项目代码都可以在这里找到: https ://stackblitz.com/edit/angular-fdcrbl
主要表格模板:
<form [formGroup]="mainForm">
<child-control formControlName="childControl"></child-control>
</form>
主窗体类:
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup } from "@angular/forms";
@Component({
selector: "my-app",
templateUrl: "./app.component.html"
})
export class AppComponent implements OnInit {
mainForm: FormGroup;
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.mainForm = this.formBuilder.group({
childControl: this.formBuilder.control("")
});
}
}
自定义子控件模板:
<div [formGroup]="childForm">
<div class="form-group">
<label translate>Child control: </label>
<input type="text" formControlName="childControl">
</div>
</div>
自定义子控件类:
import { Component, OnInit } from "@angular/core";
import { AppValidator } from "../app.validator";
import {
FormGroup,
AsyncValidator,
FormBuilder,
NG_VALUE_ACCESSOR,
NG_ASYNC_VALIDATORS,
ValidationErrors,
ControlValueAccessor
} from "@angular/forms";
import { Observable } from "rxjs";
import { map, first } from "rxjs/operators";
@Component({
templateUrl: "./child-control.component.html",
selector: "child-control",
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: ChildControlComponent,
multi: true
},
{
provide: NG_ASYNC_VALIDATORS,
useExisting: ChildControlComponent,
multi: true
}
]
})
export class ChildControlComponent
implements ControlValueAccessor, AsyncValidator, OnInit {
childForm: FormGroup;
constructor(
private formBuilder: FormBuilder,
private appValidator: AppValidator
) {}
ngOnInit() {
this.childForm = this.formBuilder.group({
childControl: this.formBuilder.control(
"",
[],
[this.appValidator.asyncValidation()]
)
});
this.childForm.statusChanges.subscribe(status => {
console.log("subscribe", status);
});
}
// region CVA
public onTouched: () => void = () => {};
writeValue(val: any): void {
if (!val) {
return;
}
this.childForm.patchValue(val);
}
registerOnChange(fn: () => void): void {
this.childForm.valueChanges.subscribe(fn);
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
isDisabled ? this.childForm.disable() : this.childForm.enable();
}
validate(): Observable<ValidationErrors | null> {
console.log('validate');
// return this.taxCountriesForm.valid ? null : { invalid: true };
return this.childForm.statusChanges.pipe(
map(status => {
console.log('pipe', status);
return status == "VALID" ? null : { invalid: true };
}),
);
}
// endregion
}