Home > OS >  Array of input on Reactive Forms
Array of input on Reactive Forms

Time:10-08

I'm trying to accomplish something like this

I have a list of plans to subscribe

{ id: 1, data: '1GB', minsms: '5.000', value: 7.5, simCards: 1 },
{ id: 2, data: '3GB', minsms: '5.000', value: 10, simCards: 3 },
{ id: 3, data: '10GB', minsms: '5.000', value: 15, simCards: 2 },
{ id: 4, data: '20GB', minsms: '5.000', value: 20, simCards: 1 },

The client can choose how many sim cards per plan and I want to put a validation (the client needs to select even if is zero cards)

I tried to use FormArray but I'm stuck. Can someone help me?

The code (I wrote a new one just for the question) https://stackblitz.com/edit/angular-ivy-mwvat5

Thanks

CodePudding user response:

you has an array of plans, and you want to manage an array of "simCards". As you only need one input, you need an FormArray of FormControls.

See that you only need an array (**), so you can simple define

simCards:FormArray

You can subscribe(*) in .ts to create the FormArray

plans:any[]
ngOnInit()
{
   this._plansService.getPlans().subscribe(res=>{
     this.plans=res;
     this.simCards=new FormArray(res.map(_=>new FormControl(0)))
   })
}

And create a function to get the control of the formArray

  getControl(index)
  {
    return this.simCards.at(index) as FormControl
  }

And you use

<!--see that iterate over simCards.controls-->
<mat-form-field *ngFor="let control of simCards.controls;let i=index">
<mat-label
  >Num of SIM cards for {{ plans[i].data }} / {{ plans[i].minsms }} at
  {{ plans[i].value | currency: 'EUR' }}</mat-label
>
  <input matInput type="text" [formControl]="getControl(i)" />
</mat-form-field>

Be carefull, the value of the simCars is only an array of "numbers" (really string), so, in submit you need make some like

submit()
{
   const values=this.plans.map((x:any,index:number)=>{
       id:x.id,
       simsCards:this.simCards.value[index]
   })
   this._plansService.save(values)
}

(*)don't forget unsubscribe!

You can also choose create a formArray of FormGroups with two properties: id and simsCards. In this case you can use

ngOnInit()
{
   this._plansService.getPlans().subscribe(res=>{
     this.plans=res;
     this.simCards=new FormArray(res.map((x:any)=>new FormGroup({
           id:new FormControl(x.id),
           simCards:new FormControl(0)
          })
     ))
   })
}

You create a function getGroup

  getGroup(index)
  {
    return this.simCards.at(index) as FormGroup
  }

And use

<mat-form-field *ngFor="let control of simCards.controls;let i=index" [formGroup]="getGroup(i)">
<mat-label
  >Num of SIM cards for {{ plans[i].data }} / {{ plans[i].minsms }} at
  {{ plans[i].value | currency: 'EUR' }}</mat-label
>
  <input matInput type="text" formControlName="simCards" />
</mat-form-field>

See that, in this case this.simCards.value give us an array of object with id and simCards

Finally, If we want to use pipe async, instead of subscribe, we can use the "tap" to create the formArray

ngOnInit(){
    this.plans$ = this._plansService.getPlans().pipe(
      tap((plan: Plan[]) => {
        this.simCards = new FormArray(
          plan.map(
            (x: any) =>
              new FormGroup({
                id: new FormControl(x.id),
                simCards: new FormControl(0),
              })
          )
        );
      })
    );
}

The .html is very similar to the above

<mat-form-field *ngFor="let plan of plans$ | async;let i=index" [formGroup]="getGroup(i)">
<mat-label
  >Num of SIM cards for {{ plan.data }} / {{ plan.minsms }} at
  {{ plan.value | currency: 'EUR' }}</mat-label
>
  <input matInput type="text" formControlName="simCards" />
</mat-form-field>

NOTE: you can also define a FormGroup with a formArray inside in the way

form=new FormGroup({
   simCards:new FormArray([])
}) 

And, as always we use a FormArray use a getter

get simCards()
{
   return this.form.get('simCards') as FormArray
}

This allow us avoid use the function getGroup() and getControl() enclosed the mat-forms in a form

In case a FormArray of FormControls

<form [formGroup]="form">
  <div formArrayName="simCards">
    <mat-form-field
      *ngFor="let plan of plans; let i = index" 
    >
      <mat-label>...</mat-label
      >
      <input matInput type="text" [formControlName]="i" />
  </mat-form-field>
  </div>
</form>

ngOnInit()
{
   this._plansService.getPlans().subscribe(res=>{
     this.plans=res;
     this.form=new FormGroup({
       simCards:new FormArray(res.map(_=>new FormControl(0)))
     })
   })
}

In case a FormArray of FormGroups

<form [formGroup]="form">
   <div formArrayName="simCards">
    <mat-form-field *ngFor="let control of simCards.controls;let i=index"
       [formGroupName]="i" >
       <label>...</label>
       <input matInput formControlName="simCards" type="text" />
    </mat-form-field>
   </div>
</form>
ngOnInit()
{
   this._plansService.getPlans().subscribe(res=>{
     this.plans=res;
     this.form=new FormGroup({
        simCards:new FormArray(res.map((x:any)=>new FormGroup({
           id:new FormControl(x.id),
           simCards:new FormControl(0)
          })
        ))
     })
   })
}

In case use pipe async we need enclosed the above form in a *ngIf in the way

<div *ngIf="{ plan: plans$ | async } as data">
  <form [formGroup]="form">
    <div formArrayName="simCards">
      <mat-form-field
        *ngFor="let control of simCards.controls; let i = index"
        [formGroupName]="i"
      >
        <mat-label
          >Num of SIM cards for {{ data.plan[i].data }} /
          {{ data.plan[i].minsms }} at
          {{ data.plan[i].value | currency: 'EUR' }}</mat-label
        >
        <input matInput type="text" formControlName="simCards" />
      </mat-form-field>
    </div>
  </form>
</div>
this.plans$ = this._plansService.getPlans().pipe(
  tap((plan: Plan[]) => {
    this.form=new FormGroup({
      simCards: new FormArray(
        plan.map(
          (x: any) =>
            new FormGroup({
              id: new FormControl(x.id),
              simCards: new FormControl(0),
            })
        )
      )

    })
  })
);
  • Related