Home > Software design >  Angular textarea with same name in loop
Angular textarea with same name in loop

Time:02-28

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.

  • Related