rahularyansharma
rahularyansharma

Reputation: 10777

How to declare FormArray for array of object in reactive forms

I have DTO in my code as follow :

export class MatrixDTO implements cols {
  constructor() {    
    this.name = "";
    this.items = [];   
    this.customRow = false;     
     this.label="";
     this.customCol=false;
  }
  name : string;
  items :Array<cols>;
  customRow:boolean;
  label: string;
  customCol:boolean;
  
}


interface cols {
  label: string;
  customCol:boolean;
}

Forms init code as follow :

    initilazieForm(): void {
        this.matrixForm = this.fb.group({      
          matrix: this.fb.array([], [Validators.required]),  
          
        });
  }

How should I declare my form array matrix so I can add new rows and columns also and same reflect when I save form.

StackBlitz

Edited as per comments :

I am looking for a final FormArray to be something like this and where customRow or customCol is false , I want to show label instead of text boxes.

[
  {
    "label": "Apple",
    "customRow": false,
    "values": [
      {
        "label": "30",
        "customCol": false
      },
      {
        "label": "30",
        "customCol": false
      },
      {
        "label": "30",
        "customCol": false
      }
    ]
  },
  {
    "label": "Bannana",
    "customRow": false,
    "values": [
      {
        "label": "50",
        "customCol": false
      },
      {
        "label": "60",
        "customCol": false
      },
      {
        "label": "70",
        "customCol": false
      }
    ]
  },
  {
    "label": null,
    "customRow": true,
    "values": [
      {
        "label": "",
        "customCol": true
      },
      {
        "label": "",
        "customCol": true
      },
      {
        "label": "",
        "customCol": true
      }
    ]
  }
]

Edit for patch value error :

#1 JSON I am getting through service while come on same form for edit : you can check only matrix node JSON ,rest is related to other controls related values.

{
"name":"rahul",
   "brand":"Brand",
   "market":"Market",
   "productName":"yVW4LHi1_Uazy2_aKVfaSw",
   "isArchived":false,
   "isCustom":false,
   "matrix":[
      {
         "label":"Apple",
         "customRow":false,
         "items":[
            {
               "label":"10",
               "customCol":false
            },
            {
               "label":"20",
               "customCol":false
            },
            {
               "label":"30",
               "customCol":false
            },
            {
               "label":"40",
               "customCol":true
            }
         ]
      },
      {
         "label":"Bannana",
         "customRow":false,
         "items":[
            {
               "label":"12",
               "customCol":false
            },
            {
               "label":"22",
               "customCol":false
            },
            {
               "label":"32",
               "customCol":false
            },
            {
               "label":"22",
               "customCol":true
            }
         ]
      },
      {
         "label":"Test",
         "customRow":true,
         "items":[
            {
               "label":"1",
               "customCol":true
            },
            {
               "label":"2",
               "customCol":true
            },
            {
               "label":"3",
               "customCol":true
            },
            {
               "label":"4",
               "customCol":true
            }
         ]
      }
   ]
}

#2 and code I am trying to use to patch values is as follow :

setFormValues(data: any) {
    console.log(data);
    this.specForm.patchValue({
      name: data.name,
      Brand: data.Brand,
      Market : data.Market,
      productName : '',
      IsArchived : data.IsArchived,
      OriginId : data.OriginId,
      IsCustom : data.IsCustom,
      specs: this.fb.array([], [Validators.required]),    
      matrix:  data.matrix.map((x:any) => this.rowGroup(x))
    });
  }

Upvotes: 0

Views: 2626

Answers (2)

Eliseo
Eliseo

Reputation: 58039

I think that it's better think first in the object you need.

I imagine you need an object like

 data=[
        {label:"Apple",values:[10,20,30]}, 
        {label:"Bannana",values:[12,22,32]}
      ];

It's an array of object with two properties, "label" and "values", values are an array on numbers.

So You need a FormArray of FormGroup with a FormControl and a FormArray

If I imagine you has an object with label and values I can make a function

  rowGroup(data:any=null){
    return new FormGroup({
       label:new FormControl(data.label),
       values:new FormArray(data.values.map(x=>new FormControl(x)))
    })
  }

So, our formArray is simple

  formArray = new FormArray(
    this.data.map(x => this.rowGroup(x))
  );

As I want to mannage the formArray directly we need a function that return the formGroup inside

  group(index)
  {
    return this.formArray.at(index) as FormGroup
  }

As we need manage the formArray "values" we use another function

  valuesArray(index)
  {
    return this.group(index).get('values') as FormArray
  }

Now we can manage the formArray using an html like

<div *ngFor="let sub of formArray.controls;let i=index">
  <div [formGroup]="group(i)">
    <input formControlName="label">
    <ng-container formArrayName="values">
      <ng-container *ngFor="let group of valuesArray(i).controls;let j=index">
        <input [formControlName]="j">
      </ng-container>
    </ng-container>
  </div>
</div>

To add a new row, the only is push a new formGroup in our formArray. To create the formGroup we use the function rowGroup passing a "data" create a doc

  addRow()
  {
    //we create an array of "values" with so many elements the first "values"
    //or with 3 elements
    const values=this.formArray.length && this.valuesArray(0)?
    this.valuesArray(0).value.map(x=>null):[null,null,null]
    
    const empty={label:null,values:values}
    this.formArray.push(this.rowGroup(empty))
  }

To add a column, we loop over the formArray.controls and add a FormControl to the "values" array. We need "cast" to FormArray and FormGroup, but it's not very complex

  addColumn()
  {
   this.formArray.controls.forEach(x=>{
     ((x as FormGroup).get('values') as FormArray).push(new FormControl(null))
   }) 
  }

The stackblitz

Update to manage the json object propused we need create a new funciton

  itemsGroup(data:any=null)
  {
    data=data || {label:null,customCol:true}
    return new FormGroup({
      label:new FormControl(data.label),
      customCol:new FormControl(data.customCol),
    })
  }

That allow us crreate the group

see a new stackblitz

Update 2 as formArray is belong to a FormGroup we are going to change a few the name of the functions. And create a getter of the formArray, so we has

  //this is our old "formArray"
  get matrixFormArray() {
    return this.specForm ? (this.specForm.get('matrix') as FormArray) : null;
  }

  //the FormGroup this is our old "group(index)"
  matrixElement(index) {
    return this.matrixFormArray.at(index) as FormGroup;
  }

  /*Function to mannage the formArray "items"*/
  itemsFormArray(index) {
    return this.matrixElement(index).get('items') as FormArray;
  }

To give value to a FormGroup we can take two aproach, use pathValue or directly create the form with the values. If we use pathValue, when we define the form we'll use some like

specForm=new FormGroup({
  name:new FormControl(),
  Brand:new FormControl(),
  Market:new FormControl(),
  ....
  matrix:new FormArray([]) //<--see in this case you defined the "matrix"
                           //   as "empty" fromArray
})

Is we are going to create the form with the data we can simply use

  specForm: FormGroup=null; //<--our FormGroup. I choose at first declare as null

But in this case is important to avoid initials error use a *ngIf in our form like

<form *ngIf="specForm" [formGroup]="specForm">
    ....
</form>

the new stackblitz with the changes

Upvotes: 1

Lautaro Lopez Ruiz
Lautaro Lopez Ruiz

Reputation: 144

you have to push formgroups into your formarray, and each object property is a formcontrol

createForm() {
    this.matrixForm = this.fb.group({      
              matrix: this.fb.array((this.matArray).map(mat => {
                            this.fb.group({
                            name: mat.name
                            ...
                      })
    }), [Validators.required]),  
         
        });
}

and add new rows like this

addMat(mat){
  this.matrixForm.get('matrix').push(this.fb.group({
  name: mat.name
  ..... 
}))
}

Upvotes: 1

Related Questions