I've created a simple nested form that depends on the child component rather than putting everything into one component. I've implemented a very basic ControlValueAccessor to enable it to work in such fashion, however what I noticed is that validation doesn't get captured at parent component. This means a faulty form can be submitted even though its not valid.
How do i get the validation of child forms to propagate up to the parent form.
Below is the code.
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 { }
}
EDIT 2: nested approach