0

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))
    });
  }

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

12 Comments

Thanks , awesome answer and great thing you correct my mistake from scratch level. I was using Reactive Forms completely wrong. Sorry I am new to angular and learning . What if I don't want to start with initial values but with a blank FormArray and add rows either from button or through checkbox list click only ?
@rahularyansharma, just only defined the formArray as empty formArray: formArray = new FormArray([]) -NOTE: In this case, check the function addColumn to check if the formArray is not empty -just update in the stackblitz-
you took in your example values as number of array , but in my case its also a group of objects , like Values:[{label:20, custCol:false},{label:20, custCol:false}] . I am trying to change the code to incldue form group in values FormArray. changing in this line values:new FormArray(data.values.map(x=>new FormControl(x))) . anyways thanks a lot for your reply.
@rahularyansharma, can you update your question indicate an example of the array you need? I saw your stackblitz and I imagine the labels was common to the values
@rahularyansharma, a FormArray can be a FormArray of FormControls or a FormArray of FormGroup -and the .html to mannage are differents, see, e.g. this SO:stackoverflow.com/questions/67700663/…. I forked your stackblitz creating a service. In the forked stackblitz I change the name of the functions to get the array matrix and write severals comments,(see the updated answer
|
1

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
  ..... 
}))
}

3 Comments

Can you please check latest stackblizt , I updated as per your suggestion but not sure why name is blank and also how I can handle items as that is also an array ?
i added an example for the input, i hope that help you

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.