Home > Mobile >  how to implement angular table with reactive form
how to implement angular table with reactive form

Time:07-08

based on my html below , as you can see I have a table and then looped through mat cell using *matCellDef="let model" , inside the cell there are input fields which are reactive forms (it has mat error checking ,formcontrolname etc).

Each cell or row should have its own reactive form since each row has its own mat form fields input fields and validations.

How do we address this in angular?. I have provided some code snippets below that might help. highly appreciated. Thanks.

#html code

<mat-table [dataSource]="table.dataSource" [@animateStagger]="{value:'50'}" matMultiSort
    (matSortChange)="table.onSortEvent()">
    <ng-container matColumnDef="id">
        <mat-header-cell *matHeaderCellDef mat-multi-sort-header="id">
            <span >COLUMN 1</span>
        </mat-header-cell>
        <mat-cell *matCellDef="let model" >
            <form [formGroup]="modelForm">
                <div fxLayout="row"  style="width: 99%;">
                    <div fxFlex="40" >
                        <mat-form-field appearance="fill" style="padding-right: 8px;" fxFlex="40">
                            <mat-label>
                                enter date
                            </mat-label>
                            <mat-date-range-input>
                                <input matStartDate placeholder="start date" required formControlName="start">
                                <input matEndDate placeholder="end date" required formControlName="end">
                            </mat-date-range-input>
                            <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
                            <mat-date-range-picker #picker></mat-date-range-picker>
                            <mat-error *ngIf="modelForm.controls.start.hasError('required')">Start date is
                                required</mat-error>
                            <mat-error *ngIf="modelForm.controls.end.hasError('required')">End date is
                                required</mat-error>
                        </mat-form-field>
            </form>

        </mat-cell>
    </ng-container>
</mat-table>

#ts code

modelForm: FormGroup;

 this.modelForm = this._createModelForm();

      private createModelForm(): FormGroup {
        return this.formBuilder.group({
           start: new FormControl(Validators.required),
           end: new FormControl(Validators.required),
        });
      }

#code that pulls data from api to be populated on table

 ngOnInit(): void {

    this.table.dataSource = new MatMultiSortTableDataSource(this.sort, this.CLIENT_SIDE);
    this.table.nextObservable.subscribe(() => { this.getDetails();});
    this.table.sortObservable.subscribe(() => { this.getDetails();});
    this.table.previousObservable.subscribe(() => {this.getDetails();});
    this.table.sizeObservable.subscribe(() => {this.getDetails();});
  }


  private getDetails() {    
    this.isLoading = true;
    this.service
    .getDetails(this.filterModel)
    .pipe(finalize(() => (this.isLoading = false)))
    .subscribe({
      next: (res) => { 
        this.table.data = res.items as Array<any>;
        this.table.totalElements = res.totalItemCount;   
      },
      error: (err) => {
        
      },
      complete: noop,
    });
  }

CodePudding user response:

In general in a mat-data-table with sort we create a dataSource like

this.dataSource=new MatTableDataSource(..An array...)

Typical the array is an array of object, but we can use an array of formGroups but in this case we need override the sortData function.

We can choose to have an array of formGroups or a FormArray (and use as ..array.. the formArray.controls. So imagine some like (with the typical ELEMENT_DATA)

//we define
dataSource:MatTableDataSource<AbstractControl>=
          new MatTableDataSource<AbstractControl>()
formArray=new FormArray([]) //<--if use a formArray
arrayOfGroup:FormGroup[]=[] //<--if use an array of FormGroups

ngAfterViewInit()
{
    this.formArray=new FormArray(ELEMENT_DATA.map(x=>this.getFormGroup(x)))
    this.dataSource=new MatTableDataSource(this.formArray.controls)

    //or
    //this.arrayOfGroup=ELEMENT_DATA.map(x=>this.getFormGroup(x))
    //this.dataSource=new MatTableDataSource(this.arrayOfGroup)

    this.dataSource.sort = this.sort;
    this.dataSource.sortData = (data: AbstractControl[], sort: MatSort) => {
      const factor=sort.direction=='asc'?1:sort.direction=='desc'?-1:0
      const column=sort.active?sort.active:'position'
      return data.sort((a: AbstractControl, b: AbstractControl) => {

        //if is number
        if (column=="position" || column=="weight")
          return factor*( a.value[column]> b.value[column]?1:
                          a.value[column]< b.value[column]?-1:
                         0)
          
        return factor*(a.value[column]>b.value[column]?1:
                         a.value[column]<b.value[column]?-1:
                         0)
      });
     }
}

getFormGroup(data:PeriodicElement)
{
    return new FormGroup({
      position:new FormControl(data.position),
      name:new FormControl(data.name),
      weight:new FormControl(data.weight),
      symbol:new FormControl(data.symbol)
   })
}

Now our cells can be like (e.g. with position)

<ng-container matColumnDef="position">
    <th mat-header-cell *matHeaderCellDef 
        mat-sort-header sortActionDescription="Sort by number">
      No.
    </th>
    <td mat-cell *matCellDef="let element"> 
       <input [formControl]="element.get('position')">
    </td>
  </ng-container>

see stackbliz

NOTE: In strict mode I feel we need an auxiliar function

  getFormControl(element:AbstractControl,column:string)
  {
    return (element as FormGroup).get(column) as FormControl
  }

And use

  <input [formControl]="getFormControl(element,'position')">

CodePudding user response:

We have a ngx-multi-sort-table. The tecnica is similar

If we see in github the source

We see that we can override the function "_sortData" that belong to the class "MatMultiSortTableDataSource", so we can do some like this in initData

  initData() {
    this.table.dataSource = new MatMultiSortTableDataSource(this.sort, this.CLIENT_SIDE);
    this.table.dataSource._sortData=(d1: AbstractControl, d2: AbstractControl, params: string[], dirs: string[]): number =>{
      if (d1.value[params[0]] > d2.value[params[0]]) {
          return dirs[0] === 'asc' ? 1 : -1;
      } else if (d1.value[params[0]] < d2.value[params[0]]) {
          return dirs[0] === 'asc' ? -1 : 1;
      } else {
          if (params.length > 1) {
              params = params.slice(1, params.length);
              dirs = dirs.slice(1, dirs.length);
              return this.table.dataSource._sortData(d1, d2, params, dirs);
          } else {
              return 0;
          }
      }
    }
    ....
  }

And when create the data we use some like

  this.table.data = res.users.map(x=>this.createGroup(x));

Another option is create a class that extends from "MatMultiSortTableDataSource"

export class MatMultiSortTableDataSourceControls<T> extends MatMultiSortTableDataSource<T> {
  constructor(sort: MatMultiSort, clientSideSorting = false) {
    super(sort,clientSideSorting);
  }
  _sortData=(dd1: T, dd2: T, params: string[], dirs: string[]): number =>{
    const d1=dd1 as any
    const d2=dd2 as any
    if (d1.value[params[0]] > d2.value[params[0]]) {
        return dirs[0] === 'asc' ? 1 : -1;
    } else if (d1.value[params[0]] < d2.value[params[0]]) {
        return dirs[0] === 'asc' ? -1 : 1;
    } else {
        if (params.length > 1) {
            params = params.slice(1, params.length);
            dirs = dirs.slice(1, dirs.length);
            return this._sortData(d1, d2, params, dirs);
        } else {
            return 0;
        }
    }
  }
}

And use

this.table.dataSource=new MatMultiSortTableDataSourceControls(this.sort,this.CLIENT_SIDE)

See a stackblitz

  • Related