When dealing with intricate FormsArrays, it is advantageous
1.-To utilize functions that retrieve the formArrays. Note that "XsArray" can act as a getter, whereas others must function as regular functions since they require an index. So
get XsArray() {
return this.form.get("Xs") as FormArray;
}
getYsArray(index) {
return this.XsArray.at(index).get("Ys") as FormArray;
}
getWsArray(index) {
return this.XsArray.at(index).get("Ws") as FormArray;
}
getZsArray(index, indexz) {
return this.getYsArray(index).at(indexz).get("Zs") as FormArray;
You can observe how the function XsArray can be used within the getWsArray function, for instance.
2.-Another aspect is to create functions that return formGroups. These functions receive either an object or null. When null is provided, we add a default object. Here they are:
setXs(el:any=null)
{
el=el||{X:null}
return this.fb.group({
X:[el.X,Validators.required],
Ys:el.Ys?this.fb.array(el.Ys.map(x=>this.setYs(x))):this.fb.array([]),
Ws:el.Ws?this.fb.array(el.Ws.map(x=>this.setWs(x))):this.fb.array([])
})
}
setYs(el:any=null)
{
el=el || {product:null}
return this.fb.group({
product:[el.product, [Validators.required]],
Zs:el.Zs?this.fb.array(el.Zs.map(x=>this.setZs(x))):this.fb.array([])
})
}
setWs(el:any=null){
el=el || {state:null,city:null}
return this.fb.group({
state: [el.state, [Validators.required]],
city: [el.city, [Validators.required]]
})
}
setZs(el:any=null)
{
el=el || {Z:null}
return this.fb.group({
Z:[el.Z, [Validators.required, Validators.pattern("[0-9]{3}])]
})
Hey! pause for a moment, what does this peculiar expression mean:
this.fb.array(el.Zs.map(x=>this.setZs(x)))
. Essentially, el.Zs represents an array, where each element of the array is transformed into a FormGroup (recall that this.setZs(x) returns a formGroup), and then a formArray is created using these formGroups. Although the concept may seem complex, it can be broken down into several steps.
//this.fb.array(el.Zs.map(x=>this.setZs(x)))
//is equivalent to
let arr=this.fb.array([])
el.Zs.forEach(x=>{
arr.push(this.setZs(x))
})
Now, everything seems more straightforward. Notice how easy it becomes to add new elements using these functions:
addX() {
this.XsArray.push(this.setXs());
}
addY(ix) {
this.getYsArray(ix).push(this.setYs());
}
addZ(ix, iy) {
this.getZsArray(ix,iy).push(this.setZs());
}
addW(ix) {
this.getYsArray(ix).push(this.setWs())
}
And removing them is just as simple, e.g.
removeW(ix, iw) {
this.getWsArray(ix).removeAt(iw)
}
Finally, in the ngOnInit method, we apply the seedData like so:
this.form = this.fb.group({
Xs: this.fb.array(this.seedData.Xs.map(x=>this.setXs(x)))
});
When displaying in .html, we also employ the aforementioned functions by iterating over the formArray.controls
<form [formGroup]="form">
<div formArrayName="Xs">
<div *ngFor="let X of XsArray.controls; let ix=index">
<div [formGroupName]="ix" class="Xs">
<input type="text" formControlName="X">
...
<div formArrayName="Ys">
<div *ngFor="let Y of getYsArray(ix).controls; let iy=index">
<div [formGroupName]="iy" class="Ys">
...
<div formArrayName="Zs">
<div *ngFor="let Z of getZsArray(ix,iy).controls; let iz=index">
<div [formGroupName]="iz" class="Zs">
....
</div>
</div>
...
</div>
</div>
</div>
...
</div>
<div formArrayName="Ws">
<div *ngFor="let W of getWsArray(ix).controls; let iw=index">
<div [formGroupName]="iw" class="Ws">
....
</div>
</div>
<input type="button" (click)="addW(ix)" value="Add W">
</div>
</div>
</div>
...
</div>
<form>
The stackblitz can be found here
NOTE: A FormArray can consist of a FormArray of FormGroup or a FormArray of FormControls. If it only has a single property (e.g. the FormArray "Zs"), typically a FormArray of FormControls should be utilized.