Angular6 Material6 Error: "There can only be one default row without a when predicate function."

Angular6Angular Material-6

Angular6 Problem Overview


I'm trying to implement the Table with expandable rows from https://material.angular.io/components/table/examples but I get this error:

There can only be one default row without a when predicate function.

This error comes from the fact that I have two <tr mat-row ..., both without a when predicate (as per example). If I remove one, then the error disappears.

From my own investigation I've found that this feature is new to Material. I upgraded to @angular/material@latest and @angular/cdk@latest and I still got the error.

Does anyone know what I have to do to implement the table as in the example from documentation? https://material.angular.io/components/table/examples

My versions:

Angular CLI: 6.0.8
Node: 8.11.2
OS: win32 x64
Angular: 6.0.4
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.6.8
@angular-devkit/build-angular     0.6.8
@angular-devkit/build-optimizer   0.6.8
@angular-devkit/core              0.6.8
@angular-devkit/schematics        0.6.8
@angular/cdk                      6.3.1
@angular/cli                      6.0.8
@angular/flex-layout              6.0.0-beta.16
@angular/material                 6.3.1
@ngtools/webpack                  6.0.8
@schematics/angular               0.6.8
@schematics/update                0.6.8
rxjs                              6.2.0
typescript                        2.7.2
webpack                           4.8.3

Angular6 Solutions


Solution 1 - Angular6

It turns out I was missing directive multiTemplateDataRows from <table> tag:

<table mat-table [dataSource]="licenses" multiTemplateDataRows>

Solution 2 - Angular6

This error could also appear if the function in your when predicate is undefined. So if you have

<mat-tree-node *matTreeNodeDef="let node; when: hasChild">

and hasChild is undefined, you could get that error.

Solution 3 - Angular6

i had same problem. i updated @angular/cdk & @angular/material to 6.4.7. And all work fine. Of course you need multiTemplateDataRows attr as well

Solution 4 - Angular6

Your template

mat-tree-node(*matTreeNodeDef="let node;when: hasChild", matTreeNodePadding)

and your component should have a method called hasChild

hasChild = (_: number, node: FlatNode) => node.expandable;

Solution 5 - Angular6

Try like this:

wrapper-table.ts

import { SelectionModel } from '@angular/cdk/collections';
import { AfterContentInit, Component, ContentChildren, Input, QueryList, TemplateRef, ViewChild } from '@angular/core';
import { MatColumnDef, MatHeaderRowDef, MatRowDef, MatTable, MatTableDataSource } from '@angular/material/table';

@Component({
  selector: 'ui-wrapper-table',
  templateUrl: './wrapper-table.component.html',
  styleUrls: ['./wrapper-table.component.scss']
})
export class WrapperTable<T> implements AfterContentInit {

  @ContentChildren(MatHeaderRowDef) headerRowDefs: QueryList<MatHeaderRowDef>;
  @ContentChildren(MatRowDef) rowDefs: QueryList<MatRowDef<T>>;
  @ContentChildren(MatColumnDef) columnDefs: QueryList<MatColumnDef>;

  @ViewChild(MatTable, { static: true }) table: MatTable<T>;

  @Input() columns: string[];

  @Input() dataSource: MatTableDataSource<T>;

  @Input() contextTemplate: TemplateRef<any>;

  @Input() selection: SelectionModel<T>;

  ngAfterContentInit() {
    this.columnDefs.forEach(columnDef => this.table.addColumnDef(columnDef));
    this.rowDefs.forEach(rowDef => this.table.addRowDef(rowDef));
    this.headerRowDefs.forEach(headerRowDef => this.table.addHeaderRowDef(headerRowDef));
  }

  isAllSelected() {
    const numSelected = this.selection && this.selection.selected.length || 0;
    const numRows = this.dataSource && this.dataSource.data.length || 0;
    return numSelected === numRows;
  }
  masterToggle() {
    this.isAllSelected() ?
      this.selection.clear() :
      this.dataSource.data.forEach(row => this.selection.select(row));
  }
  checkboxLabel(row?: T): string {
    if (!row) {
      return `${this.isAllSelected() ? 'select' : 'deselect'} all`;
    }
    return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row`;
  }
}

wrapper-table.html

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
  <ng-content></ng-content>


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

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

  <!-- Created Column -->
  <ng-container matColumnDef="created">
    <th mat-header-cell *matHeaderCellDef mat-sort-header> Criação </th>
    <td mat-cell *matCellDef="let element"> {{element.created.toDate() | date}} </td>
  </ng-container>

  <!-- Context Column -->
  <ng-container matColumnDef="context">
    <th mat-header-cell *matHeaderCellDef> Contexto </th>
    <td mat-cell *matCellDef="let element">
      <ng-container *ngTemplateOutlet="contextTemplate;context:{row:element}"></ng-container>
    </td>
  </ng-container>
</table>

companies.component.ts

import { SelectionModel } from '@angular/cdk/collections';
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatSort, MatTableDataSource } from '@angular/material';
import { CompanyModel, GetAllCompaniesUsecase } from '@apps-waves/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';


@Component({
  selector: 'wave-companies',
  templateUrl: './companies.component.html',
  styleUrls: ['./companies.component.scss']
})
export class CompaniesComponent implements OnInit, OnDestroy {
  displayedColumns: string[] = ['select', 'name', 'companyType', 'created', 'context'];
  dataSource: MatTableDataSource<CompanyModel>;
  selection: SelectionModel<CompanyModel>;

  @ViewChild('sort', { static: false }) sort: MatSort;

  onDestroy = new Subject<void>();


  constructor(
    private getAllCompanies: GetAllCompaniesUsecase
  ) { }
  
  ngOnInit() {
    this.getAllCompanies.execute()
      .pipe(takeUntil(this.onDestroy))
      .subscribe(companies => {
        this.dataSource = new MatTableDataSource(companies);
        this.selection = new SelectionModel<CompanyModel>(true, []);
        this.dataSource.sort = this.sort;
      })
  }

  onClick(company: CompanyModel) {
    console.log(company);
  }
  ngOnDestroy() {
    this.onDestroy.next();
    this.onDestroy.complete();
  }
}

companies.html

<ui-wrapper-table
  [dataSource]="dataSource"
  [columns]="displayedColumns"
  [selection]="selection"
  matSort #sort="matSort"
  [contextTemplate]="rowContext">

  <!-- Custom column definition to be provided to the wrapper table. -->
  <ng-container matColumnDef="name">
    <th mat-header-cell *matHeaderCellDef> Name </th>
    <td mat-cell *matCellDef="let element"> {{element.name}} </td>
  </ng-container>

  <ng-container matColumnDef="companyType">
    <th mat-header-cell *matHeaderCellDef> Segmento </th>
    <td mat-cell *matCellDef="let element"> {{element.companyType}} </td>
  </ng-container>

  <!-- Custom row definitions to be provided to the wrapper table. -->
  <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns; "></tr>
</ui-wrapper-table>

<ng-template #rowContext let-element="row">
  <button type="button" mat-icon-button (click)="onClick(element)">
    <mat-icon>edit</mat-icon>
  </button>
</ng-template>

<pre>{{selection?.selected | json}}</pre>

Its working pretty cool

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGoToView Question on Stackoverflow
Solution 1 - Angular6GoToView Answer on Stackoverflow
Solution 2 - Angular6adamdportView Answer on Stackoverflow
Solution 3 - Angular6Виктор МогилевскийView Answer on Stackoverflow
Solution 4 - Angular6Bertwin RomeroView Answer on Stackoverflow
Solution 5 - Angular6Gui SeekView Answer on Stackoverflow