Angular Material mat-table行分组

30

不考虑那些为其特定表格提供行分组功能的库,我正在尝试在没有此类功能的Angular Material 2 mat-table上实现此功能。

用于填充表格的项目:

export class BasketItem{
    public id: number;
    public position: number;
    public quantity: number;
    public groupId: number;
} 

在下表中将具有相同 groupId 属性的行分组。
 <mat-table class="mat-elevation-z8" [dataSource]="dataSource" multiTemplateDataRows matSort matSortActive="position" matSortDirection="asc" matSortDisableClear >

      <!-- Position Column -->  
      <ng-container matColumnDef="position">
        <mat-header-cell *matHeaderCellDef mat-sort-header>
          <b>Position</b>
        </mat-header-cell>
        <mat-cell *matCellDef="let basketItem">{{basketItem.position}}</mat-cell>
      </ng-container>

      <!-- Quantity Column -->
      <ng-container matColumnDef="quantity">
        <mat-header-cell *matHeaderCellDef>
          <b>Quantity</b>
        </mat-header-cell>
         <mat-cell *matCellDef="let basketItem">{{basketItem.quantity}}</mat-cell>
      </ng-container>

      <!-- GroupId Column -->  
      <ng-container matColumnDef="position">
        <mat-header-cell *matHeaderCellDef mat-sort-header>
          <b>GroupId </b>
        </mat-header-cell>
        <mat-cell *matCellDef="let basketItem">{{basketItem.GroupId }}</mat-cell>
      </ng-container>


      <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>

      <mat-row *matRowDef="let basketItem; columns: displayedColumns;" (click)="onSelect(basketItem)"></mat-row>

    </mat-table>

有没有关于如何处理 行分组 的想法?


你能否将这种分组方式应用于可展开行?请参见此链接中的可展开行示例:https://material.angular.io/components/table/examples。 - Mihai
3个回答

54
一个非常简单的答案是按照GroupID进行排序,这样可以将这些行分组在一起。然而,我猜你想在每个组之前显示一个标题行。
你可以提供一个替代的<mat-row *matRowDef="...",它使用了一个where子句。这可以用来显示一个非默认的列集合。where子句接受一个函数,如果该matRowDef应该被使用,则返回true。
然后,您提供给表格的数据将是数据行和组行交错排列,而函数则告诉我们哪个是哪个。以基本使用<table mat-table>为起点,手动添加组并将where子句函数添加到app/table-basic-example.ts中。
    import {Component} from '@angular/core';

    export interface PeriodicElement {
      name: string;
      position: number;
      weight: number;
      symbol: string;
    }

    export interface Group {
      group: string;
    }

    const ELEMENT_DATA: (PeriodicElement | Group)[] = [
      {group: "Group 1"},
      {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
      {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
      {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
      {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
      {group: "Group 2"},
      {position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
      {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
      {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
      {group: "Group 3"},
      {position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
      {position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
      {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
    ];

    /**
     * @title Basic use of `<table mat-table>`
     */
    @Component({
      selector: 'table-basic-example',
      styleUrls: ['table-basic-example.css'],
      templateUrl: 'table-basic-example.html',
    })
    export class TableBasicExample {
      displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
      dataSource = ELEMENT_DATA;

      isGroup(index, item): boolean{
        return item.group;
      }
    }


    /**  Copyright 2018 Google Inc. All Rights Reserved.
        Use of this source code is governed by an MIT-style license that
        can be found in the LICENSE file at http://angular.io/license */

并在app/table-basic-example.html中添加groupHeader列和额外的matRowDef。
    <mat-table [dataSource]="dataSource" class="mat-elevation-z8">

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

      <!-- Position Column -->
      <ng-container matColumnDef="position">
        <mat-header-cell *matHeaderCellDef> No. </mat-header-cell>
        <mat-cell *matCellDef="let element"> {{element.position}} </mat-cell>
      </ng-container>

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

      <!-- Weight Column -->
      <ng-container matColumnDef="weight">
        <mat-header-cell *matHeaderCellDef> Weight </mat-header-cell>
        <mat-cell *matCellDef="let element"> {{element.weight}} </mat-cell>
      </ng-container>

      <!-- Symbol Column -->
      <ng-container matColumnDef="symbol">
        <mat-header-cell *matHeaderCellDef> Symbol </mat-header-cell>
        <mat-cell *matCellDef="let element"> {{element.symbol}} </mat-cell>
      </ng-container>

      <ng-container matColumnDef="groupHeader">
        <mat-cell *matCellDef="let group">{{group.group}}</mat-cell>
      </ng-container>

      <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
      <mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
      <mat-row *matRowDef="let row; columns: ['groupHeader']; when: isGroup"> </mat-row>

    </mat-table>



    <!-- Copyright 2018 Google Inc. All Rights Reserved.
        Use of this source code is governed by an MIT-style license that
        can be found in the LICENSE file at http://angular.io/license -->

这里是一个已完成的 stackblitz,它按元素的首字母进行分组。
这是一个更完善的 stackblitz,只需提供要按列进行分组的列表,它将为您插入分组行。您还可以点击分组行来展开或折叠它们。
最后,这是一个Github项目,它修改了从material代码库中复制的MatTableDataSource类。与过滤和排序很好地配合使用,但与分页器'竞争',因为它们以不同的方式限制记录的视图。

使用您的代码,我该如何仅显示某个组,比如以“H”开头的组? - guru
那只是过滤,看这里 https://material.angular.io/components/table/overview#filtering - Stephen Turner
嗨 @guru,那是一个非常不同的话题。你应该发布一个带有一些示例代码的新问题来获得答案。 - Stephen Turner
嗨@saad,这是一个完全不同的话题。你应该发布一个新问题,并附上一些示例代码以获取答案。 - Stephen Turner
如果我在标题中添加了排序,我会遇到一个排序问题。分组会移动。 - Ebram
显示剩余2条评论

21

使用Stephen Turner的答案,现在分享这个stackblitz分支,它可以:

  • 动态发现给定数据中的列
  • 允许按需对列的不同值进行分组

正如在此线程中先前指出的那样,

使用Mat-Table进行groupBy的简单方法实际上是在显示的数据中添加行,

每个新组的开头添加的这些行可以使用@Input(matRowDefWhen)提供自定义模板。

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

<!-- Group line -->
<tr mat-row *matRowDef="let row; columns: ['groupName']; when: isAGroup"></tr>
在上面的例子中,当一行是一个组而不是初始数据的一部分时,isAGroup函数应返回true。
此外,groupName列的模板可以实现如下:
<ng-container matColumnDef="groupName">
    <td colspan="999" mat-cell *matCellDef="let group">
      {{group.name}}
    </td>
</ng-container>

最后,如果您的数据集可能会变化,可以在列模板定义上添加循环。

<ng-container *ngFor="let col of displayedColumns" [matColumnDef]="col">
    <th mat-header-cell *matHeaderCellDef>{{ col }}</th>
    <td mat-cell *matCellDef="let row">{{ row[col] }}</td>
</ng-container>

然后隐藏和显示组的行只需要根据新隐藏的组条件过滤显示的数据并刷新显示的数据即可。

对于这个已经过时的帖子表示抱歉,只是想与那些正在寻找解决方案的人分享一段可重用代码。


1
你能更加具体地解释一下你的回答吗?可以展示一些代码或数据等。 - cccnrc
感谢您的回答,我已经添加了来自之前链接的 StackBlitz 的标记样本。 - Simplicity's_Strength
1
巨大的工作。我已经能够重复使用它,在每个组行上添加可累加的数字数据。毫不害怕地说,这是一项杰出的工作,伙计 :) 非常感谢。 - Deunz
谢谢你的解决方案。这个在 Angular CLI 8 中可以工作吗? - jkr
在我的情况下,将<mat-icons></>更改为<i class = material-icons> </ i>,它就可以工作了。 - jkr
可能 <mat-icon> 没有生效是因为您没有正确导入 MatIconModule。关于此解决方案的兼容性,它仅依赖于 https://material.angular.io/components/table/api#MatRowDef。 - Simplicity's_Strength

0
你甚至可以进一步将其分解为解析编译和分组过程,就像这样:
  sortStringArray(array: string[], sortOrder: string = 'asc'): string[] {
    if(sortOrder === 'asc'){
      array.sort((a: string, b: string) => a > b ? 1 : a < b ? -1 : 0);
    }
    else if(sortOrder === 'desc'){
      array.sort((a: string, b: string) => a > b ? -1 : a < b ? 1 : 0);
    }
    return array;
  }

  groupStringArray(array: string[], sortOrder: string = 'asc'): string[] {
    let sortedArray = this.sortStringArray(array, sortOrder);
    let groupHeader = '';
    let groupedArray = [];

    for(let i = 0; i < sortedArray.length; i++){
      if(groupHeader !== sortedArray[i]){
        groupHeader = sortedArray[i];
        groupedArray.push({ isGroup: true, value: groupHeader });
        groupedArray.push(sortedArray[i]);
      }
      else{
        groupedArray.push(sortedArray[i]);
      }
    }

    return groupedArray;
  }

...并且对于您正在处理的每种不同类型的值以及您期望返回的字段,都要这样做。这样更易读和单一职责,您可以在其他地方应用这些函数,并轻松地将它们实现到表格分组中。我已经设置好了我的程序,使其处理所有类型,甚至可以通过这种清晰易读的方式进行递归处理。当您意识到以下两点时,问题变得更容易解决:1)首先进行排序可以更轻松地查找和设置分组值,2)分组只是意识到参考值不再恒定的地方。这是一种蛮力方法,但基本上就是分组逻辑的逻辑。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接