反应式表单验证不适用于 ControlValueAccessor
Reactive Form Validation not working with ControlValueAccessor
我创建了一个简单的嵌套表单,它依赖于 child 组件,而不是将所有内容都放入一个组件中。
我已经实现了一个非常基本的 ControlValueAccessor 以使其能够以这种方式工作,但是我注意到验证不会在 parent 组件处被捕获。这意味着可以提交错误的表单,即使它无效。
我如何获得 child 表单的验证以传播到 parent 表单。
下面是代码。
Parent 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>
Parent 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));
}
}
Child 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>
Child 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();
}
}
我创建了一个简单的嵌套表单,它依赖于 child 组件,而不是将所有内容都放入一个组件中。 我已经实现了一个非常基本的 ControlValueAccessor 以使其能够以这种方式工作,但是我注意到验证不会在 parent 组件处被捕获。这意味着可以提交错误的表单,即使它无效。
我如何获得 child 表单的验证以传播到 parent 表单。
下面是代码。
Parent 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>
Parent 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));
}
}
Child 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>
Child 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();
}
}