FormArray inside Angular Material Table

12,444

Solution 1

A little late to the party but I managed to get it working.

https://stackblitz.com/edit/angular-material-table-with-form-59imvq

Component

import {
  Component, ElementRef, OnInit
} from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'
import { AlbumService } from './album.service';
import { UserService } from './user.service';
import { Album } from './album.model';
import { User } from './user.model';
import { FormArray, FormGroup, FormBuilder } from '@angular/forms';
import { MatTableDataSource } from '@angular/material';

@Component({
  selector: 'table-form-app',
  templateUrl: 'app.component.html'
})
export class AppComponent implements OnInit {
  form: FormGroup;
  users: User[] = [];
  dataSource: MatTableDataSource<any>;
  displayedColumns = ['id', 'userId', 'title']
  constructor(
    private _albumService: AlbumService,
    private _userService: UserService,
    private _formBuilder: FormBuilder
    ) {}

  ngOnInit() {
    this.form = this._formBuilder.group({
      albums: this._formBuilder.array([])
    });
    this._albumService.getAllAsFormArray().subscribe(albums => {
      this.form.setControl('albums', albums);
      this.dataSource = new MatTableDataSource((this.form.get('albums') as FormArray).controls);
      this.dataSource.filterPredicate = (data: FormGroup, filter: string) => { 
          return Object.values(data.controls).some(x => x.value == filter); 
        };
    });
    this._userService.getAll().subscribe(users => {
      this.users = users;
    })
  }

  get albums(): FormArray {
    return this.form.get('albums') as FormArray;
  }

  // On user change I clear the title of that album 
  onUserChange(event, album: FormGroup) {
    const title = album.get('title');

    title.setValue(null);
    title.markAsUntouched();
    // Notice the ngIf at the title cell definition. The user with id 3 can't set the title of the albums
  }

  applyFilter(filterValue: string) {
    this.dataSource.filter = filterValue.trim().toLowerCase();
  }
}

HTML

<mat-form-field>
  <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field>

<form [formGroup]="form" autocomplete="off">
    <mat-table [dataSource]="dataSource">

      <!--- Note that these columns can be defined in any order.
            The actual rendered columns are set as a property on the row definition" -->

      <!-- Id Column -->
      <ng-container matColumnDef="id">
        <mat-header-cell *matHeaderCellDef> Id </mat-header-cell>
        <mat-cell *matCellDef="let element"> {{element.get('id').value}}. </mat-cell>
      </ng-container>

      <!-- User Column -->
      <ng-container matColumnDef="userId">
        <mat-header-cell *matHeaderCellDef> User </mat-header-cell>
        <mat-cell *matCellDef="let element" [formGroup]="element">
          <mat-form-field floatLabel="never">
            <mat-select formControlName="userId" (selectionChange)="onUserChange($event, element)" required>
              <mat-option *ngFor="let user of users" [value]="user.id">
                {{ user.username }}
              </mat-option>
            </mat-select>
          </mat-form-field>
        </mat-cell>
      </ng-container>

      <!-- Title Column -->
      <ng-container matColumnDef="title">
        <mat-header-cell *matHeaderCellDef> Title </mat-header-cell>
        <mat-cell *matCellDef="let element;" [formGroup]="element">
          <mat-form-field floatLabel="never" *ngIf="element.get('userId').value !== 3">
            <input matInput placeholder="Title" formControlName="title" required>
          </mat-form-field>
        </mat-cell>
      </ng-container>

      <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
      <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
    </mat-table>
</form>
<mat-accordion>
  <mat-expansion-panel>
    <mat-expansion-panel-header>
      <mat-panel-title>
        Form value
      </mat-panel-title>
    </mat-expansion-panel-header>
    <code>
      {{form.value | json}}
    </code>
  </mat-expansion-panel>
</mat-accordion>

Solution 2

initTreeFormArray() does not fire on init the way you need it to. So when the component is built, the html portion is looking for name, when it does not exist.

My .02 is to load a working form and subform groups on init and figure out the second function later. Also, use Mat over html table.

Share:
12,444

Related videos on Youtube

Marinescu Raluca
Author by

Marinescu Raluca

Updated on June 04, 2022

Comments

  • Marinescu Raluca
    Marinescu Raluca almost 2 years

    Note: I succeded doing FormArray inside classic HTML Table, as seen below . I want to have a FormArray inside Angular Material table and to populate it with data. I tried the same approach as with classic HTML Table, but i was not able to compile it due error "Could not find column with id 'name''"

    <div class="form-group">
    <form [formGroup]="myForm" role="form">
      <div formArrayName="formArrList">
        <table>
          <tr>
            <th>Name</th>
            <th>School</th>
    
          </tr>
    
          <tr *ngFor="let list of myForm.get('formArrList').controls;let i = index" [formGroupName]="i">
            <td>
              <div class="col-sm-6">
                <input class="form-control" type="text"  formControlName="name"/>
              </div>
            </td>
            <td>
              <div class="col-sm-6">
                <input class="form-control" type="text"  formControlName="school"/>
              </div>
            </td>
          </tr>
        </table>
      </div>
    </form>
    

    I try to have a FormArray inside my Angular Material Table Here is my HTML file

    <div>
    <form [formGroup]="myForm" role="form">
      <ng-container formArrayName="formArrList">
    
      <mat-table #table [dataSource]="myDataSource">
        <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
        <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
    
    
        <ng-container *ngFor="let detailsItems of myForm.get('formArrList').controls;let i = index" [formGroupName]="i">
    
    
        <ng-container matColumnDef="name">
          <mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
          <mat-cell *matCellDef="let element"> 
    
            <mat-form-field class="" hideRequiredMarker>
          <input matInput formControlName="name" type="text" class="form-control"
                 autocomplete="off"
                 placeholder="name">
          </mat-form-field>
    
          </mat-cell>
        </ng-container>
    
    
    
        <ng-container matColumnDef="school">
          <mat-header-cell *matHeaderCellDef>School</mat-header-cell>
          <mat-cell *matCellDef="let element"> 
    
           <mat-form-field class="" hideRequiredMarker>
          <input matInput formControlName="school" type="text" class="form-control"
                 autocomplete="off"
                 placeholder="school">
          </mat-form-field>
          </mat-cell>
        </ng-container>
    
        </ng-container>
    
    
    
      </mat-table>
      </ng-container>
    
    </form>
    

    And here is a part of my .TS file

    @Component(..)
    export class DemO implements OnInit {
    
     displayedColumns = ['name', 'school'];
      myForm: FormGroup;
    
      formArrList: FormArray;
    
       myDataSource: DataSource;
       dummyData: Element[] = [];
    
    
       ngOnInit(): void {
    
        //init form arrayTree
        this.myForm = this.formBuilder.group({
          'formArrList': new FormArray([])
        });
    
      }
    
        initTreeFormArray(name: string, school: string) {
        return this.formBuilder.group({
          'name': [code_any,],
          'school': [prio,]
        });
      }
    
    
      renderTableOnButtonClick(){
           const control = <FormArray>this.treeForm.controls['formArrList']; 
           control.push(this.initTreeFormArray("DummyName", "DummySchool", element.name));
    
    
    
          this.dummyData.push({name: "DummyName", school: "DummySchool"});
          this.myDataSource = new sDataSource(this.dummyData);
    
    
    }
    
    • Marinescu Raluca
      Marinescu Raluca about 6 years
      anyone with an ideea?
  • Marinescu Raluca
    Marinescu Raluca about 6 years
    I want to use Angular Material Table over classic HTML but i can't use it due error, Could not find column with id 'name''"
  • user7728112
    user7728112 about 6 years
    Why do you need the matColumnDef="name" at all? formControlName="name" will bind provided its under the form group instance.
  • Mohamad Chami
    Mohamad Chami over 4 years
    matSort does not work, i tried to add sorting on the columns but i failed, do you have a solution for this problem???