【问题标题】:Angular Material Table rowspan columns based on dataSource object array property size基于数据源对象数组属性大小的角度材质表行跨列
【发布时间】:2019-05-20 06:55:58
【问题描述】:

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

这是我的距离(短?):

https://stackblitz.com/edit/angular-wudscb

上面 Stackblitz 中的示例“几乎”是我正在寻找的,但我不知道如何完成它。

...
===============================================
||     ||            ||            ||  row1  ||
||  1  ||  Hydrogen  ||   1.0079   ||========||
||     ||            ||            ||  row2  ||
===============================================
||     ||            ||            ||  row1  ||
||     ||            ||            ||========||
||  2  ||   Helium   ||   4.0026   ||  row2  ||
||     ||            ||            ||========||
||     ||            ||            ||  row3  ||
===============================================
||     ||            ||            ||  row1  ||
||  3  ||  Lithium   ||   6.941    ||========||
||     ||            ||            ||  row2  ||
===============================================
...

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

https://stackblitz.com/edit/angular-lnahlh

按照我的 Stackblitz(第一个链接),我的问题是:

我是否离实现这个行跨度 shim/hack 太远了?

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

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

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

【问题讨论】:

  • @MaihanNijat 在材料布局中打开的 Github 问题上找到的示例以非常不同的方式工作。您可以在 sabe Github 问题中找到一个问题,即当对象 dataSource 中有一个数组时如何解决。
  • 你能编辑你的问题并添加预期的输出吗?
  • @Justcode 已编辑。
  • 我建议您在对象中添加一个数据字段,而不是使用方法getRowSpan()。数据绑定到方法的迭代代价很高。

标签: angular angular-material


【解决方案1】:

好吧,材料表似乎没有 api 文档,我也找不到任何技巧来做到这一点,但是我们可以调整我们的数据来支持这一点,根据您的第二个示例,我们可以将数据改造成新的json,我们可以得到我们预期的结果。

第 1 步:

    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

第二步

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

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

第三步

因为它是根据您的第二个链接

  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;
    }
  }

第四步

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

    <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>

Here is the demo

【讨论】:

  • 我试图用这个示例实现拖放行,根据实现它只是隐藏跨行并只显示单行,所以当我使用拖放功能时它不会移动整个跨行,而不是它只从跨行中移出一行。你能帮忙吗?
  • @Nofi 您需要显示更多详细信息,创建示例或单独的问题。
  • 嗨@Justcode这个答案对我非常有用。但是当我们有另一列带有 stringArray(两列带有 stringArray[])时,我们如何显示它。请举个例子。
  • @Hari9513 mat-row 有这个功能,请探索它你会知道的
【解决方案2】:

因为我对提供的答案不满意(尤其是 cacheSpan 实现)。

恕我直言,我想出了一些更方便的方法,例如:

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

【讨论】:

  • 我试过这个,当我指向使用服务获取远程数据时,rowSpan 没有按预期工作。当我使用相同的 JSON 而不从服务中提取它时,它按预期工作。
  • @aswininayak 数据来自哪里没有区别,一定是您的实施有问题...
  • 谢谢凯尔 this.spans 对象分配中存在一些排序问题
【解决方案3】:

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

为您提供 2 种方法: 1-格式化您的数据,每行保留一个描述,与第二个href中的示例数据[{id, name, weight, countdescriptions, description},...]相同,并使用[attr.rowspan]='data.countdescriptions'而不是[attr.rowspan]='getRowSpan(data.id)'。 2- 更新内容格式,如&lt;td&gt; 描述中的&lt;ul&gt;&lt;li *ngFor...,并删除[attr.rowspan] 属性。

【讨论】:

    【解决方案4】:

    一种更简单的方法是使用 ng-containers

    html 模板是这样的,例如:app.component.html

    <table mat-table [dataSource]="dataSource" class="mat-elevation-z8" multiTemplateDataRows>
    
    <ng-container matColumnDef="id">
        <th mat-header-cell *matHeaderCellDef> Priority </th>
           <ng-container *matCellDef="let data">
              <td mat-cell *ngIf="data.id" [attr.rowspan]="rowSpanData[data.id]">
                {{ data.id }} 
              </td>
           </ng-container>
    </ng-container>
    
    <ng-container matColumnDef="name">
        <th mat-header-cell *matHeaderCellDef> Name </th>
        <ng-container *matCellDef="let data">
          <td mat-cell *ngIf="data.name" [attr.rowspan]="rowSpanData[data.id]">
           {{ data.name }} 
          </td>
        </ng-container>
    </ng-container>
    
    <ng-container matColumnDef="weight">
        <th mat-header-cell *matHeaderCellDef> Weight </th>
        <ng-container *matCellDef="let data">
          <td mat-cell *ngIf="data.name" [attr.rowspan]="rowSpanData[data.id]">
           {{ data.weight }} 
          </td>
        </ng-container>
    </ng-container>
    
    <ng-container matColumnDef="descriptions">
        <th mat-header-cell *matHeaderCellDef> Descriptions </th>
        <td mat-cell *matCellDef="let data"> {{ data.description }} </td>
    </ng-container>
    
    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
    

    JS/TS文件内容会是

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'my-app',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      dataSource = ELEMENT_DATA;
      rowSpanData = ROWSPANDATA;
      displayedColumns = ['id', 'name', 'weight', 'descriptions'];
      constructor() {  }
    }
    
    export interface PeriodicElement {
      id: number;
      name: string;
      weight: number;
      descriptions: string[];
    }
    
    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'] },
    ]
    
    let DATA1 = []
    const ROWSPANDATA = {}
    originalData.forEach(row => {
      ROWSPANDATA[row.id] = row.descriptions.length;
      row.descriptions.forEach((desc, index) => {
        if (index === 0) {
          DATA.push({id: row.id, name: row.name, weight: row.weight, description: 
          desc});
        } else {
         DATA.push({description: desc})
        }
      })
    })
    
    const ELEMENT_DATA: PeriodicElement[] = DATA;
    

    只需重新格式化数据以适应基本的 HTML 表格。

    working example here

    注意:ID 字段可以重复,这里为了简单起见,我使用了 ID,您可以创建自己的唯一键并将其添加为行数据的一部分,让它不显示为表格列,这仍然可以带角料台。

    【讨论】:

      【解决方案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'] }
      ]
      

      如果我们只显示其中一个元素,我们应该这样写:

      <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
      

      这会给我们:


      现在,如果我们想要迭代数据,我们必须改变一些事情。

      技巧 1

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

      技巧 2

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

      技巧 3

      行必须有一个单独的迭代器,但我们已经得到了第一个元素。所以我们必须进行这个单独的迭代(使用另一个*ngFor),但如果索引高于0,就显示它。

      <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>
      

      最后,我们有:

      【讨论】:

      • 我理解你的例子,但它不使用材料表(mat-table),也不保留任何 JavaScript 功能(过滤、排序......)。
      猜你喜欢
      • 2020-07-29
      • 1970-01-01
      • 2021-03-20
      • 2019-07-10
      • 2018-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多