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

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

我正在尝试从 https://material.angular.io/components/table/examples 实现 Table with expandable rows,但出现此错误:

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

这个错误是因为我有两个 <tr mat-row ...,都没有 when 谓词(根据示例)。如果我删除一个,错误就会消失。

根据我自己的调查,我发现此功能是 Material 的新功能。我升级到@angular/material@latest 和@angular/cdk@latest 但我仍然遇到错误。

有谁知道我必须做什么才能实现文档示例中的 table? https://material.angular.io/components/table/examples

我的版本:

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

原来我缺少 <table> 标签中的指令 multiTemplateDataRows

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

我有同样的问题。我将@angular/cdk & @angular/material 更新到 6.4.7。一切正常。当然你也需要 multiTemplateDataRows 属性

如果 when 谓词中的函数未定义,也可能出现此错误。所以如果你有

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

hasChild 未定义,您可能会遇到该错误。

这样试试:

包装器-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`;
  }
}

包装器-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>

它的工作很酷

您的模板

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

并且您的组件应该有一个名为 hasChild

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