This might be a simple question, but I was not able to find the answer to this question anywhere.
I have a form that has input fields inside a ngFor
loop. The code is like following:
component.ts
remarksFormInit() {
this.remarksForm = this.formBuilder.group({
fremark: [null, Validators.required]
});
}
template.html
<div *ngFor="let relectedLeave of selectedLeaves">
<div [formGroup]="remarksForm">
<div >
<label >Remarks</label>
<textarea formControlName="fleaveRemark" [(ngModel)]="remarks">
</textarea>
</div>
</div>
</div>
What I am looking for is something like in html
<textarea name="fleaveRemark[]"></textarea>
so that I will get all the values entered in the textarea in the component when the save button is clicked. But now when I enter a value in one of the textarea, it is reflected in all the other fields. I think this is because of the ngModel
.
But I am not sure how to implement this functionality. I tried using FormArray
but seems like it is for a different purpose. Can anyone please help ?
CodePudding user response:
As always, a FormArray can be a FormArray of FormControls or a FormArray of FormGroups. A FormArray of FormControls are to store an array of "string" (or array of "number") a FormArray of FormGroup is to store an array of objects)
You only want to store an array, so you need a FormArray of Controls
form=new FormGroup({
remarks:new FormArray([new FormControl(null,Validators.required)])
})
As always we manage a FormArray we use a "getter" of the FormArray
get remarks()
{
return this.form.get('remarks') as FormArray
}
And typical functions to add and remove an element of the FormArray
addRemark(data:string=null)
{
this.remarks.push(new FormControl(data,Validators.required))
}
removeRemark(index:number)
{
this.remarks.removeAt(index)
}
The .html. See that we use in the input [formControlName]="i"
and there're no formGroupName
<form [formGroup]="form">
<button (click)="addRemark()">add</button>
<div formArrayName="remarks">
<div *ngFor="let control of remarks.controls; let i = index">
<textarea [formControlName]="i"></textarea>
<button (click)="removeRemark(i)">remove</button>
</div>
</div>
</form>
Well, this is a typical formArray inside a FormGroup. Really if we only want a FormArray, we needn't use a FormGroup. We can define directly
remarks=new FormArray([new FormControl(null,Validators.required)]);
The problem, now is that we can not use [formControlName]="i"
when we iterate. We can think in write some like [formControl]="remarks.at(i)"
but Angular say us that "Type 'AbstractControl' is missing the following properties from type 'FormControl'". That's mean that Angular doesn't know that is really FormControl. So we need a function that returns our FormControl
getControl(index:number)
{
return this.remarks.at(i) as FormControl
}
And know we can use [formControl]
<button (click)="addRemark()">add</button>
<div *ngFor="let control of remarks.controls; let i = index">
<textarea [formControl]="getControl(i)"></textarea
>
<button (click)="removeRemark(i)">remove</button>
</div>
In this stackblitz you have the two approaches using a FormArray
Well, about adding elements dynamically or not. You can have an array with data, e.g.
data=["one","two","three"...]
When you create the formArray you can use "map"
remarks=new FormArray(this.data.map(x=>new FormControl(x,Validators.required))
Looks complex but see that the only we make is transform the array "this.data" (an array of strings) into an array of FormControls. (is the function of map). Then we use the array to create the FormArray. If we can make more "step by step"
const arrayOfFormControls=this.data.map(x=>new
FormControl(x,Validators.required)
remarks=new FormArray(arrayOfFormControls)
or even more "step" instead use map use a forEach
const arrayOfFormControls=[] //<--a empty array
this.data.forEach(x=>
{
arrayOfFormControls.push(new FormControl(x,Validators.required)
})
remarks=new FormArray(arrayOfFormControls)
CodePudding user response:
This is a different approach you could try. Instead of using FormArrays
just using a key value FormGroup
. here is the stackblitz link
initializeLeavesForm(): void {
this.selectedLeaves.forEach(({ id, reason }) => {
this.leavesForm.addControl(
id.toString(),
new FormGroup({
reason: new FormControl(reason),
})
);
});
}
essentially this function would run when you get the leaves from your backend. For each of the leave youd have an id and in the main leavesForm
form group you add a new form group. The name of this form group would be the id
of the leave. Inside of the form group would be the reasons form control or any other control you need.
its run on ngOnInit
since right now theres no backend.
<div *ngFor="let leave of selectedLeaves">
<div [formGroup]="getRemarksFormGroup(leave.id)">
<label>Remarks</label>
<br/>
<textarea formControlName="reason"></textarea>
</div>
<button (click)="getValue(leave.id)">Get Val</button>
</div>
over here you loop over the leaves and get their id. running the function getRemarksFromGroup(leave.id)
returns the form group corresponding to the leave. And then youd bind this with the input and since each id has a different form group and control you dont end up editing one and editing all others.
Ideally rather than a function or getter it'd be more optimal to implement a pipe.