AkRoy
AkRoy

Reputation: 343

mat table with dynamically adding rows and editable fields in a row with validatons

I have just started working on the Angular reactive forms and i was trying to build a table which is inside a form.

The table has add new feature by clicking it new empty row will be inserted in the table. the existing rows will be in edit mode by default and they are validated. The table data will be saved with a single save button which is out of the table but inside the form. I tried the below code

constructor(private router: Router, private fb: FormBuilder) { }
  columnsToDisplay: string[];
  dataList;
  copyDataList;
  rows: FormArray = this.fb.array([]);
  formGroup: FormGroup = this.fb.group({ actualsVolumeData: this.rows });

  ngOnInit() {
    this.columnsToDisplay = ['id', 'code', 'desc'];

    this.formGroup = this.fb.group({
      columns: this.columnsToDisplay,
    });
    this.copyDataList = [];
    this.dataList = [];
    let list = [
      {
        code: 'one',
        desc: 'One1',
        id: 1
      },
      {
        code: 'two',
        desc: 'Two1',
        id: 2
      },
      {
        code: 'three',
        desc: 'Three1',
        id: 3
      },
    ];
    this.copyDataList = new MatTableDataSource(list);
    this.dataList = new MatTableDataSource(list);
  }

  onAdd() {
    let newRow = {
      id: this.dataList.data.length + 1,
      code: undefined,
      desc: undefined
    }
    this.copyDataList.data.push(newRow);
    this.dataList = new MatTableDataSource(this.copyDataList.data);
  }

  onSubmit() {

  }
<form [formGroup]=`formGroup`>
  <button mat-button (click)=`onAdd()`>Add</button>
  <table mat-table [dataSource]=`dataList` [formArrayName]=`actualsVolumeData` class=`mat-elevation-z8`>

    <ng-container matColumnDef=`id`>
      <th mat-header-cell *matHeaderCellDef> ID </th>
      <td mat-cell *matCellDef=`let element, let i = index` [formGroupName]=`i`> {{element.id}} </td>
    </ng-container>

    <ng-container matColumnDef=`code`>
      <th mat-header-cell *matHeaderCellDef> Code </th>
      <td mat-cell *matCellDef=`let element, let i = index` [formGroupName]=`i`> {{element.code}}
        <mat-form-field>
          <input matInput formControlName='code'>
        </mat-form-field>
      </td>
    </ng-container>

    <ng-container matColumnDef=`desc`>
      <th mat-header-cell *matHeaderCellDef> Description </th>
      <td mat-cell *matCellDef=`let element, let i = index` [formGroupName]=`i`> {{element.desc}}
        <mat-form-field>
          <input matInput formControlName=`desc`>
        </mat-form-field>
      </td>
    </ng-container>

    <tr mat-header-row *matHeaderRowDef=`columnsToDisplay`></tr>
    <tr mat-row *matRowDef=`let row; columns: columnsToDisplay;`></tr>
  </table>
<button mat-button (click)=`formGroup.valid && onSubmit()`>submit</button>
</form>

but am getting this error this.validator is not a function

Upvotes: 1

Views: 5720

Answers (1)

Eliseo
Eliseo

Reputation: 57929

How make a formArray from data

Imagine you has a data like

[
 {code: 'one',desc: 'One1',id: 1},
 {code: 'two',desc: 'Two1',id: 2},
 {code: 'three',desc: 'Three1',id: 3}
]

To create a formArray it's useful has a function that, received an object and return a FormGroup

createFormGroup(data):FormGroup
{
   data=data|| {code:'',desc:'',id:0}
   return new FormGroup({
       code:new FormControl(data.code,Validators.required),
       desc:new FormControl(data.desc,Validators.required),
       id:new FormControl(data.id,Validators.required)
   })
}

if we call to the function with an object return a formGroup, if we call the function with null, return also a FormGroup with the elements empty

When you has the data you can do simple

this.myFormArray=new FormArray(this.data.map(x=>this.createFormGroup(x)))

That's each element of data convert to a formGroup, the formArray will be an array with this elements. map transform each element "x" in "this.createFormGroup(x)"

If you has a service that return the data you subscribe

this.myService.getData().subscribe(res=>{
     this.myFormArray=new FormArray(res.map(x=>x.this.createFormGroup(x)))
})

//your service has a method like

getData()
{
     return this.httpClient("http:myUrl")
}

The good of this aproach is that to add a new element to the FormArray you only need make

this.formArray.push(this.createFormGroup(null)) 

To remove

this.formArray.removeAt(index)

In stackblitz has a little example of all this. Well, in service I use the rxjs operator of normally was a httpClient.get(url). Only I put the part of create the formArray,

NOTE: I use the contructor of FormGroup and the constructor of FormArray, but you can use BuilderForm like

createFormGroup(data):FormGroup
{
   data=data|| {code:'',desc:'',id:0}
   return this.fb.group({
       code:[data.code,Validators.required],
       desc:[data.desc,Validators.required],
       id:[data.id,Validators.required]
   })
}

And use

myFormArray=this.fb.array(res.map(x=>x.this.createFormGroup(x)))

Upvotes: 1

Related Questions