在使用 NG-ZORRO
的树状表格时,官方提供的 demo是一次性加载全部数据的。在项目中,数据比较多的情况下,一次性获取全部数据,是非常影响用户体验的。所以实现树状表格惰性加载数据。
创建一个组件
$ ng g c sluggishness-tree-table
Your global Angular CLI version (10.2.0) is greater than your local
version (7.3.9). The local Angular CLI version is used.
To disable this warning use "ng config -g cli.warnings.versionMismatch false".
CREATE src/app/pages/sluggishness-tree-table/sluggishness-tree-table.component.less (0 bytes)
CREATE src/app/pages/sluggishness-tree-table/sluggishness-tree-table.component.html (42 bytes)
CREATE src/app/pages/sluggishness-tree-table/sluggishness-tree-table.component.spec.ts (735 bytes)
CREATE src/app/pages/sluggishness-tree-table/sluggishness-tree-table.component.ts (336 bytes)
UPDATE src/app/pages/pages.module.ts (3085 bytes)
(base)
实现
- 模版文件
<nz-table #expandTable
[nzLoading]="loading"
[nzData]="listOfMapData">
<thead>
<tr>
<th nzWidth="40%">Name</th>
<th nzWidth="30%">Age</th>
<th>Address</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let data of expandTable.data">
<ng-container *ngFor="let item of mapOfExpandedData[data.key]">
<tr *ngIf="(item.parent && item.parent.expand) || !item.parent">
<td
[nzIndentSize]="item.level * 20"
[nzShowExpand]="!!item.children"
[(nzExpand)]="item.expand"
(nzExpandChange)="collapse(mapOfExpandedData[data.key], item, $event)">
{{ item.name }}
</td>
<td>{{ item.age }}</td>
<td>{{ item.address }}</td>
</tr>
</ng-container>
</ng-container>
</tbody>
</nz-table>
- 类文件
import { Component, OnInit } from '@angular/core';
import { timer } from 'rxjs';
export interface TreeNodeInterface {
key: number;
name: string;
age: number;
level?: number;
expand?: boolean;
address: string;
children?: TreeNodeInterface[];
}
@Component({
selector: 'app-sluggishness-tree-table',
templateUrl: './sluggishness-tree-table.component.html',
styleUrls: ['./sluggishness-tree-table.component.less']
})
export class SluggishnessTreeTableComponent implements OnInit {
public loading = false;
public listOfMapData = [
{
key: 1,
name: 'John Brown sr.',
age: 60,
address: 'New York No. 1 Lake Park',
children: []
},
{
key: 2,
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
children: []
}
];
mapOfExpandedData: { [key: string]: TreeNodeInterface[] } = {};
constructor() {
}
ngOnInit(): void {
this.listOfMapData.forEach(item => {
this.mapOfExpandedData[item.key] = this.convertTreeToList(item);
});
}
collapse(array: TreeNodeInterface[], data: TreeNodeInterface, $event: boolean): void {
if ($event === false) {
if (data.children) {
data.children.forEach(d => {
const target = array.find(a => a.key === d.key)!;
target.expand = false;
this.collapse(array, target, false);
});
} else {
return;
}
}
if ($event) {
if (data.children.length === 0) {
this.loading = true;
timer(1000).subscribe(() => {
this.loading = false;
const childData: TreeNodeInterface[] = [
{
key: Math.random(),
name: `John Brown${Math.random()}`,
age: 42,
address: 'New York No. 2 Lake Park',
children: []
},
{
key: Math.random(),
name: `John Brown jr.${Math.random()}`,
age: 30,
address: 'New York No. 3 Lake Park',
children: []
}
];
data.children = childData;
Array.from(childData).forEach((child) => {
const childObj = {
...child,
level: data.level + 1,
expand: false,
parent: data,
children: []
};
// 插入到具体的节点中
if (!array.map(opt => opt.key).includes(child.key)) {
const childParentIndex = array.map(opt => opt.key).indexOf(data.key);
array.splice(childParentIndex + 1, 0, childObj);
}
});
});
}
}
}
convertTreeToList(root: object): TreeNodeInterface[] {
const stack: any[] = [];
const array: any[] = [];
const hashMap = {};
stack.push({ ...root, level: 0, expand: false });
while (stack.length !== 0) {
const node = stack.pop();
this.visitNode(node, hashMap, array);
if (node.children) {
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push({ ...node.children[i], level: node.level + 1, expand: false, parent: node });
}
}
}
return array;
}
visitNode(node: TreeNodeInterface, hashMap: { [key: string]: any }, array: TreeNodeInterface[]): void {
if (!hashMap[node.key]) {
hashMap[node.key] = true;
array.push(node);
}
}
}
- 效果图
具体实现
这里大部分代码其实和 NG-ZORRO
给出的demo差不多,重点的代码就是 nzExpandChange
的回调函数 collapse
的实现
collapse(array: TreeNodeInterface[], data: TreeNodeInterface, $event: boolean): void {
if ($event === false) {
if (data.children) {
data.children.forEach(d => {
const target = array.find(a => a.key === d.key)!;
target.expand = false;
this.collapse(array, target, false);
});
} else {
return;
}
}
if ($event && data.children.length === 0) {
this.loading = true;
timer(1000).subscribe(() => {
this.loading = false;
const childData: TreeNodeInterface[] = [
{
key: Math.random(),
name: `John Brown${Math.random()}`,
age: 42,
address: 'New York No. 2 Lake Park',
children: []
},
{
key: Math.random(),
name: `John Brown jr.${Math.random()}`,
age: 30,
address: 'New York No. 3 Lake Park',
children: []
}
];
// 将获取的值赋值给当前这个item
data.children = childData;
Array.from(childData).forEach((child) => {
const childObj = {
...child,
level: data.level + 1,
expand: false,
parent: data,
children: []
};
// 插入到具体的节点中
if (!array.map(opt => opt.key).includes(child.key)) {
const childParentIndex = array.map(opt => opt.key).indexOf(data.key);
array.splice(childParentIndex + 1, 0, childObj);
}
});
});
}
}
这里$event
为false
时,树折叠起来,这里可以不用处理,增加一个$event===true
的时候的逻辑。
使用timer(1000)
设置一个超时函数来模拟http
请求,childData
模拟时远端服务器返回的数据。
- 代码
data.children = childData;
将获取到的数据赋值给当前元素的
children
- 代码
Array.from(childData).forEach((child) => {
const childObj = {
...child,
level: data.level + 1,
expand: false,
parent: data,
children: []
};
// 插入到具体的节点中
if (!array.map(opt => opt.key).includes(child.key)) {
const childParentIndex = array.map(opt => opt.key).indexOf(data.key);
array.splice(childParentIndex + 1, 0, childObj);
}
});
为获取到的每一个元素设置
level
、expand
、parent
、children
字段。然后将这个元素插入到对应的节点中。
需要注意
- 数据中要存在主键
数据中要有一个字段是作为主键存在的,不能重复,当前的demo中为key
字段,这个主键字段在转成map
以后作为map
的key
存在。所以这里模拟后端取的数据中,数据中key
字段的值是取随机数。
详细代码已经上传到GitHub