基于dataSource对象数组属性大小的Angular Material Table行跨列

Angular Material Table rowspan columns based on dataSource object array property size

即使在Angular Material的7.2版中,我似乎也找不到关于如何在mat-table上使用rowpan并保持组件功能的示例。

这是多远(简短?):

上面Stackblitz中的示例几乎是我正在寻找的东西,但是我看不到如何完成它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
===============================================
||     ||            ||            ||  row1  ||
||  1  ||  Hydrogen  ||   1.0079   ||========||
||     ||            ||            ||  row2  ||
===============================================
||     ||            ||            ||  row1  ||
||     ||            ||            ||========||
||  2  ||   Helium   ||   4.0026   ||  row2  ||
||     ||            ||            ||========||
||     ||            ||            ||  row3  ||
===============================================
||     ||            ||            ||  row1  ||
||  3  ||  Lithium   ||   6.941    ||========||
||     ||            ||            ||  row2  ||
===============================================
...

使用其他元数据格式的示例可以在以下位置找到:

在我的Stackblitz(第一个链接)之后,我的问题是:

我距离实现这种行间填充/ hack太远了吗?

如何根据row ['descriptions']大小的长度来循环行?

如果对象内部还有另一个数组属性怎么办? 我可以对其大小进行迭代并生成列/行/行跨度,这样它会变得更通用吗?

我正在尝试为社区找到通用的解决方案。


好吧,似乎材料表没有用于它的api文档,我也找不到做到这一点的任何技巧,但是我们可以选择我们的数据来支持这一点,根据您的第二个示例,我们可以将数据重新构建为新的json和我们可以获得预期的结果。

第1步 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    const originalData = [
      { id: 1, name: 'Hydrogen', weight: 1.0079, descriptions: ['row1', 'row2'] },
      { id: 2, name: 'Helium', weight: 4.0026, descriptions: ['row1', 'row2', 'row3'] },
      { id: 3, name: 'Lithium', weight: 6.941, descriptions: ['row1', 'row2'] },
      { id: 4, name: 'Beryllium', weight: 9.0122, descriptions: ['row1', 'row2', 'row3'] },
      { id: 5, name: 'Boron', weight: 10.811, descriptions: ['row1'] },
      { id: 6, name: 'Carbon', weight: 12.0107, descriptions: ['row1', 'row2', 'row3'] },
      { id: 7, name: 'Nitrogen', weight: 14.0067, descriptions: ['row1'] },
      { id: 8, name: 'Oxygen', weight: 15.9994, descriptions: ['row1'] },
      { id: 9, name: 'Fluorine', weight: 18.9984, descriptions: ['row1', 'row2', 'row3'] },
      { id: 10, name: 'Neon', weight: 20.1797, descriptions: ['row1', 'row2', 'row3'] },
    ]; //original data

    const DATA = originalData.reduce((current, next) => {
      next.descriptions.forEach(b => {
        current.push({ id: next.id, name: next.name, weight: next.weight, descriptions: b })
      });
      return current;
    }, []);//iterating over each one and adding as the description
    console.log(DATA)

    const ELEMENT_DATA: PeriodicElement[] = DATA; //adding data to the element data

第2步

这将是您的第二个stackblitz链接。

1
2
3
 getRowSpan(col, index) {    
    return this.spans[index] && this.spans[index][col];
  }

第三步

就像你第二个链接一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  constructor() {
    this.cacheSpan('Priority', d => d.id);
    this.cacheSpan('Name', d => d.name);
    this.cacheSpan('Weight', d => d.weight);
  }

  /**
   * Evaluated and store an evaluation of the rowspan for each row.
   * The key determines the column it affects, and the accessor determines the
   * value that should be checked for spanning.
   */
  cacheSpan(key, accessor) {
    for (let i = 0; i < DATA.length;) {
      let currentValue = accessor(DATA[i]);
      let count = 1;

      // Iterate through the remaining rows to see how many match
      // the current value as retrieved through the accessor.
      for (let j = i + 1; j < DATA.length; j++) {
        if (currentValue != accessor(DATA[j])) {
          break;
        }

        count++;
      }

      if (!this.spans[i]) {
        this.spans[i] = {};
      }

      // Store the number of similar values that were found (the span)
      // and skip i to the next unique row.
      this.spans[i][key] = count;
      i += count;
    }
  }

第4步

使用索引向下传递到rowpan并隐藏不需要的行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    <ng-container matColumnDef="id">
        <th mat-header-cell *matHeaderCellDef> Priority </th>
        <td mat-cell *matCellDef="let data;let i = dataIndex" [attr.rowspan]="getRowSpan('Priority',i)" [style.display]="getRowSpan('Priority', i) ? '' : 'none'">
         {{ data.id }} </td>
    </ng-container>

    <ng-container matColumnDef="name">
        <th mat-header-cell *matHeaderCellDef> Name </th>
        <td mat-cell *matCellDef="let data;let i = dataIndex" [attr.rowspan]="getRowSpan('Name',i)" [style.display]="getRowSpan('Name', i) ? '' : 'none'">
         {{ data.name }} </td>
    </ng-container>

    <ng-container matColumnDef="weight">
        <th mat-header-cell *matHeaderCellDef> Weight </th>
        <td mat-cell *matCellDef="let data;let i = dataIndex" [attr.rowspan]="getRowSpan('Weight',i)" [style.display]="getRowSpan('Weight', i) ? '' : 'none'">
         {{ data.weight }} </td>
    </ng-container>

    <ng-container matColumnDef="descriptions">
        <th mat-header-cell *matHeaderCellDef> Descriptions </th>
        <td mat-cell *matCellDef="let data"> {{ data.descriptions }} </td>
    </ng-container>

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


</table>

这是演示


由于我对给出的答案(尤其是cacheSpan实现)不满意。

我想出了一些更方便的恕我直言,类似于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
export class TableBasicExample {
  displayedColumns = ['priority', 'status', 'dateCreated', 'testNumber', 'testCurrency', 'testTime'];
  dataSource = DATA;

  spans = {};

  constructor() {
    this.spans = Object.assign({}, {
      priority: this.spanDeep(['priority'], DATA),
      status: this.spanDeep(['priority', 'status'], DATA),
      dateCreated: this.spanDeep(['priority', 'status', 'dateCreated'], DATA)
    });
  }

  spanDeep(paths: string[] | null, data: any[]) {
    if (!paths.length) {
      return [...data]
        .fill(0)
        .fill(data.length, 0, 1);
    }

    const copyPaths = [...paths];
    const path = copyPaths.shift();

    const uniq = uniqWith(data, (a, b) => get(a, path) === get(b, path))
      .map(item => get(item, path));

    return uniq
      .map(uniqItem => this.spanDeep(copyPaths, data.filter(item => uniqItem === get(item, path))))
      .flat(paths.length);
  }

  getRowSpan(path, idx) {
    return this.spans[path][idx];
  }
};

可以在这里找到工作示例:https://stackblitz.com/edit/angular-lnahlh-hw2d3b


我们必须说那里有多少行,但是有些行具有相同的id,如果它们使用相同的ID,我们将对td进行排序和合并。
但是对于您的数据,据说那里有一些行,并且描述可以数组和可拆分。通过这种方式,JS无法知道应该有多少

2种方法供您选择:
1-格式化数据,每行保留一个描述,与第二个href [{id, name, weight, countdescriptions, description},...]中的示例数据相同,然后使用[attr.rowspan]='data.countdescriptions'代替[attr.rowspan]='getRowSpan(data.id)'
2-更新内容格式,如描述

中的

  • ,并删除[attr.rowspan]属性。


    有一些技巧可以使这项工作。其他答案已经解决了,但我将尝试其他方法。

    假设您的数据如下(我从其他答案之一中得出):

    1
    2
    3
    4
    5
    elements = [
        { id: 1, name: 'Hydrogen', weight: 1.0079, descriptions: ['row1', 'row2'] },
        { id: 2, name: 'Helium', weight: 4.0026, descriptions: ['row1', 'row2', 'row3'] },
        { id: 3, name: 'Lithium', weight: 6.941, descriptions: ['row1', 'row2'] }
    ]

    如果只显示其中一个元素,则应编写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Name</th>
          <th>Weight</th>
          <th>Descriptions</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td rowspan="2">1</td>
          <td rowspan="2">Hidrogen</td>
          <td rowspan="2">1.0079</td>
          <td>row1</td>
        </tr>
        <tr>
          <td>row2</td>
        </tr>
      </tbody>
    </table

    这将使我们:

    enter image description here

    现在,如果要迭代数据,则需要更改某些内容。

    绝招1

    如果我们在tr标记内使用*ngFor,则表将崩溃。相反,我们只需要在ng-container标记内使用它。

    把戏2

    我们必须将rowspan更改为[attr.rowspan]

    绝招3

    这些行必须有一个单独的迭代器,但是我们已经有了第一个元素。因此,我们必须进行此分离的迭代(使用另一个*ngFor),但是如果索引大于0则仅显示它。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Name</th>
          <th>Weight</th>
          <th>Descriptions</th>
        </tr>
      </thead>
      <tbody>
        <ng-container *ngFor="let e of elements">
          <tr>
            <td [attr.rowspan]="e.descriptions.length + 1">{{e.id}}</td>
            <td [attr.rowspan]="e.descriptions.length + 1">{{e.name}}</td>
            <td [attr.rowspan]="e.descriptions.length + 1">{{e.weight}}</td>
            <td>{{e.descriptions[0]}}</td>
          </tr>
          <tr *ngFor="let d of e.descriptions; let index = index">
            <td *ngIf="index>0">{{d}}</td>
          </tr>
        </ng-container>
      </tbody>
    </table>

    最后,我们有:

    enter image description here