'Splitting complex Reactive Form into sub components doesn't validate or link to parent
I have a massive form that i am trying to break down into child components to make it more reusable.
However I don't know how to link it all back to the parent efficiently. I've been doing some research and noticed people using FormGroupDirective, in conjuction with ControlValueAccessor but cannot pin down a working example to learn from.
Scenario is simple, I want to capture staff and their direct reports, and that can be 1..N levels deep.
Below is the simplified setup of the form and setup i got to so far to demonstrate what I am trying to do.
Before all of this was in a single page which is not good practice.
Department.TS
export class DepartmentComponent implements OnInit {
departmentForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.departmentForm= this.fb.group({
name: [],
directReports: this.fb.array([]),
});
}
get directReports(): FormArray {
return this.departmentForm.controls['directReports'] as FormArray;
}
addStaff() {
this.directReports.push(this.newStaff());
}
newStaff() {
var staffMember = this.fb.group({
name: [],
directReports: [],
});
return staffMember ;
}
}
Department.HTML
<ng-container [formGroup]="departmentForm">
<mat-form-field>
<mat-label>Name</mat-label>
<input matInput formControlName="name" />
</mat-form-field>
<ng-container formArrayName="directReports">
<app-staff *ngFor="let staff of directReports.controls" formGroupName="staff">
</app-staff>
</ng-container>
</ng-container>
<button (click)="addStaff()">Add Staff</button>
<pre>
{{ departmentForm.value | json }}
</pre>
Child component
export class StaffComponent implements OnInit {
staffForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.staffForm= this.fb.group({
name: [],
directReports: this.fb.array([]),
});
}
get directReports(): FormArray {
return this.staffForm.controls['directReports'] as FormArray;
}
addStaff() {
this.directReports.push(this.newStaff());
}
newStaff() {
var staffMember = this.fb.group({
name: [],
directReports: [],
});
return staffMember ;
}
}
Child HTML
<ng-container [formGroup]="staffForm">
<mat-form-field>
<mat-label>Name</mat-label>
<input matInput formControlName="name" />
</mat-form-field>
<ng-container formArrayName="directReports">
<app-staff *ngFor="let staff of directReports.controls" formGroupName="staff">
</app-staff>
</ng-container>
</ng-container>
<button (click)="addStaff()">Add Staff</button>
Solution 1:[1]
The easer way is pass as @Input the FormGroup/FormArray
If your app-staff is like
export class StaffComponent {
staffForm:FormGroup
@Input('staffForm') set _(value){
this.staffForm=value as FormGroup //<--I change this line
}
}
<div [formGroup]="staffForm">
<input formControlName="name">
<input formControlName="directReports">
</div>
You can pass the "formGroup" in parent
<ng-container *ngFor="let staff of directReports.controls;let i=index">
<app-staff [staffForm]="directReports.at(i)">
</app-staff>
</ng-container>
See how you pass the "formGroup". In this way you define the whole formGroup "departmentForm" in parent. When you pass as @Input a complex object, you pas a "reference" so, any change in any property of the object is "reflected" in the object.
Update if we want that our component can be deleted we can add in StaffComponent some like
<button (click)="delete()">delete</button>
delete()
{
const parent=this.staffForm.parent as FormArray;
const index=parent.controls.findIndex(x=>x==this.staffForm)
parent.removeAt(index)
}
Or use an @Output if we want the "parent" was who really remove the element.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
