0

I'm learning Angular and its Angular Material UI Framework. After installing dependencies, I generated an Material Rank Table with this command:

ng generate @angular/material:table rank-table --module=app

I manually edited it to add a new column named extra to the data displayed in the date. The generated code is this way:

    <!-- Id Column -->
    <ng-container matColumnDef="id">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>Id</th>
      <td mat-cell *matCellDef="let row">{{row.id}}</td>
    </ng-container>

    <!-- Name Column -->
    <ng-container matColumnDef="name">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
      <td mat-cell *matCellDef="let row">{{row.name}}</td>
    </ng-container>

    <!-- extra Column -->
    <ng-container matColumnDef="extra">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>Extra</th>
      <td mat-cell *matCellDef="let row">{{row.extra}}</td>
    </ng-container>

This code duplication annoys me, since the class rank-table.component.ts has an attribute defined as displayedColumns = ['id', 'name', 'extra'];.

How could I put this code inside a loop on displayedColumns? If it isn't possible, I'll accept the answer that explains to me why.

neves
  • 33,186
  • 27
  • 159
  • 192

2 Answers2

0

I have found an example of dynamic columns implementation and apply it to your needs.

Try to use the following code :

HTML :

<mat-table #table [dataSource]="dataSource">

  <!-- Generic column definition -->
  <ng-container *ngFor="let column of columns" [cdkColumnDef]="column.columnDef">
    <mat-header-cell *cdkHeaderCellDef>{{ column.header }}</mat-header-cell>
    <mat-cell *cdkCellDef="let row">{{ column.cell(row) }}</mat-cell>
  </ng-container>

  <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
  <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>

</mat-table>

Typescript :

    export class AppComponent {
        columns = [
        { columnDef: 'id', header: 'Id.',    cell: (element: MyObject) => `${element.id}` },
        { columnDef: 'name',     header: 'Name',   cell: (element: MyObject) => `${element.name}`     },
        { columnDef: 'extra',   header: 'Extra', cell: (element: MyObject) => `${element.extra}`   },
        ];
    
        displayedColumns = this.columns.map(c => c.columnDef);
        dataSource = new ExampleDataSource();
    }
    
    export interface MyObject {
        id: number;
        name: string;
        extra: string;
    }
    
    const data: MyObject[] = [
        {id: 1, name: 'Hydrogen', extra: 'H'},
        {id: 2, name: 'Helium',  extra: 'He'},
        {id: 3, name: 'Lithium',  extra: 'Li'},
        {id: 4, name: 'Beryllium',  extra: 'Be'},
        {id: 5, name: 'Boron', extra: 'B'},
    ];
    
    /**
     * Data source to provide what data should be rendered in the table. The observable provided
     * in connect should emit exactly the data that should be rendered by the table. If the data is
     * altered, the observable should emit that new set of data on the stream. In our case here,
     * we return a stream that contains only one set of data that doesn't change.
     */
    export class ExampleDataSource extends DataSource<any> {
        /** Connect function called by the table to retrieve one stream containing the data to render. */
        connect(): Observable<MyObject[]> {
        return Observable.of(data);
        }
    
        disconnect() {}
    }

Github issue : link (with explanation)

Oussail
  • 2,200
  • 11
  • 24
0

if better use a simple

<mat-cell *cdkCellDef="let row">{{ row[column.columnDef]}}</mat-cell>

You neend'y create a function

   //INNECESARY
   cell: (element: MyObject) => `${element.id}`
Eliseo
  • 50,109
  • 4
  • 29
  • 67