1

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

AkRoy
  • 343
  • 4
  • 10
  • Do you have a stackblitz link which reproduces the error? – Joep Kockelkorn Sep 29 '19 at 14:16
  • you has an example in https://stackoverflow.com/questions/56566003/array-of-formgroup-within-mat-table-for-each-row/56571113#56571113 and comments. If you want a more complex example about how add/delete rows and move across inputs using arrows keys https://stackblitz.com/edit/angular-wmfjhh-dmkoyx?file=app%2Ftable-basic-example.ts – Eliseo Sep 29 '19 at 14:27
  • @Eliseo, How can i make the myformArray = new FormArray([ dynamic. Because am getting data from backend. from the stackoverflow link – AkRoy Sep 29 '19 at 15:53
  • I put it as response, a comment has not enought space :) – Eliseo Sep 29 '19 at 16:51

1 Answers1

1

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)))
Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • https://stackblitz.com/edit/angular-mqqv3d I did some changes but i have a doubt about the html part, Can you please help @Eliseo – AkRoy Oct 01 '19 at 10:42
  • it's only change the service to return data and use as dataSource the formArray.controls, see your forked stackblitz.https://stackblitz.com/edit/angular-wmfjhh-nimhmk?file=app%2Ftable-basic-example.ts there're errors, but I'm in hurry – Eliseo Oct 01 '19 at 14:18
  • one last issue am facing is, i have wrapped the table with the form and placed a submit button at he end. The formArray is not showing correct valid status of the form. If once the fromArray.valid get false it is not becoming true, even if i provide correct detail. The newly added row is optional, but once you submit some value then the entire row becomes required. But the formArray.valid is not working. The individual control is working fine. – AkRoy Oct 02 '19 at 16:08
  • https://stackblitz.com/edit/angular-mqqv3d?file=src%2Fapp%2Fapp.component.css or stackblitz.com/edit/angular-mqqv3d code updated @Eliseo – AkRoy Oct 02 '19 at 16:43
  • can you help in wrapping the table in a form, because i have some validations to do with form tag? – AkRoy Oct 03 '19 at 14:04
  • @akroy did you happen to figure out the HTML part? Is there anyway you could add that to the answer here? Thank you! – Brandon Apr 06 '20 at 05:19