3
votes

For some reason I'm getting a error when making a checkbox column for my data table.

I understand the error from the console, but what can I do to fix it?

view.component.html

<mat-card-content>
<div class="view-container mat-elevation-z8">
  <table mat-table [dataSource]="dataSource" matSort>

    <ng-container matColumnDef="column" *ngFor="let column of displayedColumns">
      <th mat-header-cell *matHeaderCellDef>
        <mat-checkbox (change)="$event ? masterToggle() : null"
                      [checked]="selection.hasValue() && isAllSelected()"
                      [indeterminate]="selection.hasValue() && !isAllSelected()">
        </mat-checkbox>
      </th>
      <td mat-cell *matCellDef="let action">
        <mat-checkbox (click)="$event.stopPropagation()"
                      (change)="$event ? selection.toggle(row) : null"
                      [checked]="selection.isSelected(row)">
        </mat-checkbox>
      </td>
    </ng-container>

    <ng-container [matColumnDef]="column" *ngFor="let column of displayedColumns">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>
        {{ column }}
        <mat-icon aria-hidden="false" aria-label="filter icon">more_horiz</mat-icon>
      </th>
      <td mat-cell *matCellDef="let action">{{ action[column] }}
      </td>
    </ng-container>

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

  <mat-paginator [pageSizeOptions]="pageSizeOptions" showFirstLastButtons></mat-paginator>
</div>

view.component.ts

displayedColumns: string[] = [];

const displayedColumns = this.viewData.Columns.map((c: { Name: any; }) => c.Name);
    displayedColumns[2] = 'Folder1';
    this.displayedColumns = displayedColumns;
    // tslint:disable-next-line: max-line-length
    const fetchedData = this.viewData.DataRows.map((r: { slice: (arg0: number, arg1: number) => { forEach: (arg0: (d: any, i: string | number) => any) => void; }; }) => {
      const row = {};
      r.slice(0, 9).forEach((d: any, i: string | number) => (row[this.displayedColumns[i]] = d));
      return row;
    });
2
You gotta show us your columns. Can you post displayedColumns? That is where the duplicates are. Make sure the collection contains unique values.Damian C
displayedColumns posted..kjamp
"displayedColumns" must have ended up with duplicate values in it. Can you try this logging the contents? console.log(JSON.stringify(displayedColumns || []));Merkle Groot
I'm getting a 'ERROR Error: Duplicate column definition name provided: "column".' error but I believe that's related to the HTML ng-containers. What I'm trying to do is just add a checkbox for a rowkjamp
Do you need checkboxes for each column or just one checkbox per row?coreuter

2 Answers

4
votes

I think your problem is you are using each value in displayedColumns more than one time for in the matColumnDef First here:

...
    <ng-container matColumnDef="column" *ngFor="let column of displayedColumns">
      <th mat-header-cell *matHeaderCellDef>
        <mat-checkbox (change)="$event ? masterToggle() : null"
                      [checked]="selection.hasValue() && isAllSelected()"
                      [indeterminate]="selection.hasValue() && !isAllSelected()">
        </mat-checkbox>
...

Then here:

...
   <ng-container [matColumnDef]="column" *ngFor="let column of displayedColumns">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>
        {{ column }}
        <mat-icon aria-hidden="false" aria-label="filter icon">more_horiz</mat-icon>
      </th>
      <td mat-cell *matCellDef="let action">{{ action[column] }}
      </td>
    </ng-container>
...

In fact, if you need a checkbox per row you don't need to create a mat-checkbox per displayedColumns.

Here we have the example of Angular Material: https://material.angular.io/components/table/overview#selection

So, you should do the following changes:

1) Add the select column to displayedColumns:

view.component.ts

displayedColumns: string[] = [];

const displayedColumns = this.viewData.Columns.map((c: { Name: any; }) => c.Name);
    displayedColumns[2] = 'Folder1';
    this.displayedColumns = ['select'].concat(displayedColumns);
    // tslint:disable-next-line: max-line-length
    const fetchedData = this.viewData.DataRows.map((r: { slice: (arg0: number, arg1: number) => { forEach: (arg0: (d: any, i: string | number) => any) => void; }; }) => {
      const row = {};
      r.slice(0, 9).forEach((d: any, i: string | number) => (row[this.displayedColumns[i]] = d));
      return row;
    });

2) The first column (checkbox) should be select and it shouldn't be included in the *ngFor of the other columns:

view.component.html

<mat-card-content>
<div class="view-container mat-elevation-z8">
  <table mat-table [dataSource]="dataSource" matSort>

    <ng-container matColumnDef="select">
      <th mat-header-cell *matHeaderCellDef>
        <mat-checkbox (change)="$event ? masterToggle() : null"
                      [checked]="selection.hasValue() && isAllSelected()"
                      [indeterminate]="selection.hasValue() && !isAllSelected()">
        </mat-checkbox>
      </th>
      <td mat-cell *matCellDef="let row">
        <mat-checkbox (click)="$event.stopPropagation()"
                      (change)="$event ? selection.toggle(row) : null"
                      [checked]="selection.isSelected(row)">
        </mat-checkbox>
      </td>
    </ng-container>

    <ng-container [matColumnDef]="column" *ngFor="let column of displayedColumns | slice:1 ">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>
        {{ column }}
        <mat-icon aria-hidden="false" aria-label="filter icon">more_horiz</mat-icon>
      </th>
      <td mat-cell *matCellDef="let action">{{ action[column] }}
      </td>
    </ng-container>

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

  <mat-paginator [pageSizeOptions]="pageSizeOptions" showFirstLastButtons></mat-paginator>
</div>

Now it should work as expected.

3
votes

You're getting the error because you are looping twice over the displayedColumns.

In order to fix the error you need to:

  • fix the definition of the "select" column
  • exclude your select column id from the displayedColumns to prevent using it multiple times
  • include the select column id in the list of ids for the row definitions

Select column

<ng-container matColumnDef="select">
   <!-- ... -->
</ng-container>

Here you need to remove *ngFor="..." if you want to display only one select column with a checkbox for each row. If you want multiple select columns (e.g one for each data column), you need to define a list of unique ids for those columns and loop over them:

<ng-container matColumnDef="select_{{col}}" *ngFor="let col of dataColumns">
   <!-- e.g. dataColumns = ["id", "col1", "col2"]; 
        would result in select_id, select_col1, select_col2 -->
   <!-- ... -->
</ng-container>

Other columns

<ng-container [matColumnDef]="column" *ngFor="let column of dataColumns">
   <!-- ... -->
</ng-container>

Replace displayedColumns with a list which includes all column ids excluding the id(s) of the select column(s).

Rows

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

Finally use a list of column ids which contains your data column ids as well as the id(s) of the select column(s)

Have a look at this Stackblitz where I've added examples for one and multiple select columns. (Note: I didn't adjust the select logic for the multi select column example..)