2
  • I need to create a table form with Angular which is based on a simple 2-dimensional array of arrays.
  • The data structure is simple but the data in the cells is interdependent. The single cells are accessed by indices alone e.g., row:1, col:0.
  • Custom validators need to be defined on on cell-level. In addition, there may be validators for rows and columns.

I tried various ways to define FormArray holding an array of FormControls. But I am unsure how to access the respective FormControl by row and column indices alone in the Angular template.

Model

[
  ['a', 'b'],
  ['c', 'd']
]

FormGroup

  form = new FormGroup({
    rows: new FormArray([...])
  });

Expected result

I am tried various things similar to this:

  <form [formGroup]="form"">
    <div formArrayName="rows">
      <div 
        *ngFor="let row of rows.controls; let rowIndex = index" [formGroupName]="rowIndex">
        <div formArrayName="cols">
          <div
            *ngFor="let col of form.get('cols').controls; let colIndex = index"
            [formGroupName]="colIndex">
            <input [formControlName]="colIndex" />
          </div>
        </div>
      </div>
    </div>
  </form>
1

2 Answers 2

6

Dabbel, if you has an Array of Array, create a FormArrays of FormArrays (sorry for the joke)

Well, imagine you has data=[ ['a', 'b'], ['c', 'd'] ]

You can in ngOnInit create the formArray of FormArray like

//At firs a empty FormArray
this.formArray = new FormArray([]);
//with each element of data
this.data.forEach(x => {
  //x is e.g.['a','b']
  //we create a emptt FormArray
  const obj = new FormArray([]);
  //add a FormControl
  x.forEach(y => {
    obj.push(new FormControl(y));
  });
  //and push in the formArray
  this.formArray.push(obj);
});

or abreviated using map like

this.formArray=new FormArray(
  this.data.map(x=>new FormArray(
    x.map(y=>new FormControl(y))))
)

Well, How mannage a FormArray outside a FormGroup? If our FormArray is a FormArray of FormGroup, we make in general

 <!--yes we can use [formGroup] with a FormArray-->
<form [formGroup]="formArray">
   <!--iterate over the formArray.controls, that is a formGroup-->
  <div *ngFor="let group of formArray.controls;let i=index">
     <div [formGroup]="group">
       <input formControlName="name">
        ...
     </div>
  </div>
</form>

Well, our formArray is a FormArray of FormArray, but remember that we using [formGroup] with an array and iterate over formArray.controls.

<form [formGroup]="formArray">
    <div *ngFor="let subarray of formArray.controls;let i=index">
        <div [formGroup]="subarray">
            <ng-container *ngFor="let control of subarray.controls;let j=index">
                <input [formControl]="control">{{control.invalid?"*":""}}
       </ng-container>
        </div>
    </div>
</form>

NOTE: I use <ng-container> nor <div> to not create addicionals divs. (We can not put the *ngFor in the own input because, then, we can not have access to control.invalid

Well, as you want create Validators, we are changing a bit when we create the formGroup to include the validators, I put a "fool" example

this.formArray=new FormArray(
  this.data.map(x=>new FormArray(
    x.map(y=>new FormControl(y,Validators.required)),
    this.rowValidator())),this.arrayValidator()
)

And ours validators can be like

  rowValidator()
  {
    return (array:FormArray)=> {
      const invalid:boolean=array.value.
             filter((x,index)=>array.value.indexOf(x)!=index).length>0
      return invalid?{error:'must be different'}:null
    }
  }
  arrayValidator()
  {
      return (array:FormArray)=> {
        let arrayJoin="";
        array.value.forEach(x=>arrayJoin+=x.join(''))
        return arrayJoin=="abcd"?null:{error:'must be a,b,c,d'}
      }
  }

You can see in the stackblitz

NOTE: In a real application, we neen't use so many Validators. Take account the cost of this validator on the perfomance of the app

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

2 Comments

Thank you for your thorough explanation, Eliseo! You were right about FormArray of FormArray, of course. I'll just mention for the benefit of others that in this exampleformArray.status and formArray.invalid works with FormArray, too. Again, thank you for explaining it in detail.
Thank for the comment <!--yes we can use [formGroup] with a FormArray-->
0

Template forms is going to make your life a lot easier here.

Just bind directly to the array itself.

<form>
  <div *ngFor="let row of data;let i=index">
    <input [name]="prop1_ + i" [(ngModel)]="row.prop1">
    <input [name]="prop2_ + i" [(ngModel)]="row.prop2">
  </div>
</form>

Comments

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.