angular - 反应式表单验证不适用于 ControlValueAccessor
问题描述
我创建了一个简单的嵌套表单,它依赖于子组件,而不是将所有内容都放在一个组件中。我已经实现了一个非常基本的 ControlValueAccessor 以使其能够以这种方式工作,但是我注意到验证不会在父组件中捕获。这意味着即使它无效,也可以提交错误的表单。
我如何获得子表单的验证以传播到父表单。
下面是代码。
父 HTML
<form [formGroup]="parentForm" (ngSubmit)="submitData()">
<h3>Parent</h3>
<br>
<ng-container formArrayName="children">
<ng-container *ngFor="let c of children?.controls; index as j">
<app-child [formControlName]="j"></app-child>
</ng-container>
</ng-container>
<button mat-raised-button type="button" (click)="addChild()">Add Activity</button>
<button mat-raised-button type="submit" color="warn" [disabled]="!parentForm.valid">Submit</button>
</form>
<pre>
{{parentForm.valid}} // this is always true because its not getting validator state from children
</pre>
<pre>
{{parentForm.value | json}}
</pre>
家长 TS
export class ParentComponent implements OnInit {
parentForm: FormGroup;
constructor(private fb: FormBuilder) {
}
ngOnInit(): void {
this.createForm();
}
private createForm() {
this.parentForm = this.fb.group({
children: this.fb.array([])
});
}
get children(): FormArray {
return this.parentForm.get("children") as FormArray;
}
addChild() {
const tempChild = new FormControl({
description: null
});
this.children.push(tempChild);
}
submitData() {
console.info(JSON.stringify(this.parentForm.value));
}
}
子 HTML
<form [formGroup]="newChildForm">
<tr>
<td>
<mat-form-field>
<mat-label>Description</mat-label>
<input type="text" matInput formControlName="description" required>
</mat-form-field>
</td>
</tr>
</form>
儿童 TS
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ChildComponent),
multi: true
}
]
})
export class ChildComponent implements ControlValueAccessor, OnInit {
newChildForm: FormGroup;
constructor(private fb: FormBuilder) {
this.createForm();
}
private createForm() {
this.newChildForm = this.fb.group({
description: [null, Validators.required],
});
}
onTouched: () => void = () => { };
writeValue(value: any) {
if (value) {
this.newChildForm.setValue(value, { emitEvent: true });
}
}
registerOnChange(fn: (v: any) => void) {
this.newChildForm.valueChanges.subscribe((val) => {
fn(val);
});
}
setDisabledState(disabled: boolean) {
disabled ? this.newChildForm.disable() : this.newChildForm.enable();
}
registerOnTouched(fn: () => void) {
this.onTouched = fn;
}
ngOnInit(): void { }
}
编辑 2:嵌套方法
解决方案
您可以通过在您的子组件中实现Validator接口来实现这一点,这需要validate
在子组件中编写您的方法,以便在每次值更改时进行检查,在您的情况下可能如下所示:
@Component({
selector: "app-child",
templateUrl: "./child.component.html",
styleUrls: ["./child.component.css"],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ChildComponent),
multi: true,
},
{
// >>>>>> 1- The NG_VALIDATORS should be provided here <<<<<
provide: NG_VALIDATORS,
useExisting: ChildComponent,
multi: true,
},
],
})
export class ChildComponent implements ControlValueAccessor, Validator, OnInit {
newChildForm: FormGroup;
onChange: any = () => {};
onValidationChange: any = () => {};
constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) {
this.createForm();
}
private createForm() {
this.newChildForm = this.fb.group({
description: [null, Validators.required],
children: this.fb.array([]),
});
}
onTouched: () => void = () => {};
writeValue(value: any) {
if (value) {
this.newChildForm.setValue(value, { emitEvent: true });
}
}
registerOnChange(fn: (v: any) => void) {
this.onChange = fn;
}
setDisabledState(disabled: boolean) {
disabled ? this.newChildForm.disable() : this.newChildForm.enable();
}
registerOnTouched(fn: () => void) {
this.onTouched = fn;
}
ngOnInit(): void {
this.newChildForm.valueChanges.subscribe((val) => {
this.onChange(val);
// >>>> 4- call registerOnValidatorChange after every changes to check validation of the form again <<<<
this.onValidationChange();
});
}
// >>>> 2- validate method should be added here <<<<
validate(): ValidationErrors | null {
if (this.newChildForm?.invalid) {
return { invalid: true };
} else {
return null;
}
}
// >>>> 3- add registerOnValidatorChange to call it after every changes <<<<
registerOnValidatorChange?(fn: () => void): void {
this.onValidationChange = fn;
}
get children(): FormArray {
return this.newChildForm.get("children") as FormArray;
}
addChild() {
const tempChild = new FormControl({
description: null,
children: [],
});
this.children.push(tempChild);
this.cdr.detectChanges();
}
}