即使在Angular Material 7.2版中,我似乎也找不到如何在mat-table上使用rowpan并保持组件功能的示例。
这是我(很短)的距离:
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(第一个链接)之后,我的问题是:
我距离实现这种跨行填充/ hack太远了吗?
如何根据行['descriptions']大小的长度来循环行?
如果我在对象内有另一个数组属性怎么办?我可以迭代并生成具有其大小的column / rows / rowspan,这样它会变得更通用吗?
我正在尝试为社区找到通用的解决方案。
答案 0 :(得分:4)
好吧,似乎材料表没有用于它的api文档,我也找不到任何技巧来实现这一点,但是我们可以选择我们的数据来支持这一点,根据您的第二个示例,我们可以将数据重构为新的json,我们可以获得预期的结果。
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;
}
}
使用索引向下传递到rowpan并隐藏不需要的行
<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>
答案 1 :(得分:1)
我们必须说那里有多少行,但是有些行具有相同的id
,如果它们使用相同的ID,我们将对td进行排序和合并。
但是对于您的数据,据说那里有一些行,并且描述可以数组和可拆分。通过这种方式,JS无法知道应该有多少<tr>
。
2种方法供您选择:
1-格式化数据,每行保留一个描述,与第二个href [{id, name, weight, countdescriptions, description},...]
中的示例数据相同,并使用[attr.rowspan]='data.countdescriptions'
代替[attr.rowspan]='getRowSpan(data.id)'
。
2-更新内容格式,例如描述<ul><li *ngFor...
中的<td>
,并删除[attr.rowspan]
属性。
答案 2 :(得分:1)
由于我对给出的答案(尤其是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
答案 3 :(得分:0)
一种更简单的方法是使用 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 表格。
注意:ID 字段可以重复,这里我使用 ID 来简化答案,您可以创建自己的唯一键并将其添加为行数据的一部分,让它不显示为表列,这仍然可以带角材料表。
答案 4 :(得分:-1)
有一些技巧可以使此项工作。其他答案已经解决了,但我将尝试其他方法。
假设您的数据如下(我从其他答案之一中得出):
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
这将给我们:
现在,如果要迭代数据,则必须进行一些更改。
如果我们在*ngFor
标记内使用tr
,则表将崩溃。相反,我们只需要在ng-container
标签内使用它即可。
我们必须将rowspan
更改为[attr.rowspan]
。
行必须有一个单独的迭代器,但是我们已经有了第一个元素。因此,我们必须进行此分离的迭代(使用另一个*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>
最后,我们有: