html

*matTreeNodeDef="let node"

matTreeNodePadding

[matTreeNodePaddingIndent]="10"

(click)="choseNode(node)"

[ngClass]="{ 'node--highlight': node.isChosen }"

>

[node]="node"

class="hover-row"

(delNode)="delNode($event)"

(editNode)="editNode($event)"

(addNode)="addNode($event)"

>

*matTreeNodeDef="let node; when: hasChild"

matTreeNodePadding

[matTreeNodePaddingIndent]="20"

(click)="choseNode(node)"

[ngClass]="{ 'node--highlight': node.isChosen }"

>

mat-icon-button

[attr.aria-label]="'Toggle ' + node.item"

matTreeNodeToggle

>

{{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }}

[node]="node"

class="hover-row"

(delNode)="delNode($event)"

(editNode)="editNode($event)"

(addNode)="addNode($event)"

>

*ngIf="node.isLoading"

mode="indeterminate"

class="example-tree-progress-bar"

>

ts import { Component, OnInit, Injectable } from '@angular/core';

import {

CollectionViewer,

SelectionChange,

DataSource,

} from '@angular/cdk/collections'; //定义和操作数据源(DataSource)和视图容器(CollectionViewer)以及选择状态的变化(SelectionChange)

import { FlatTreeControl } from '@angular/cdk/tree'; //管理扁平节点的展开和折叠状态

import { BehaviorSubject, merge, Observable } from 'rxjs'; //创建可观察对象和处理异步数据流

import { map } from 'rxjs/operators'; // 从 RxJS 库中导入 map 操作符,用于对可观察对象发出的值进行转换。

import { TreeService } from './services/tree.service'; // 接口

// 树组件

export class DynamicFlatNode {

//定义了一个 DynamicFlatNode 类,表示一个扁平节点(Flat node),包含可展开状态和层级信息

constructor(

public item: string,

public level = 1,

public expandable = false,

public isLoading = false,

public isChosen = false

) {}

}

@Injectable({ providedIn: 'root' }) //提供服务的装饰器语法。它的作用是将这个服务注册为根注入器的提供者。

export class DynamicDataSource implements DataSource {

// 定义了一个名为 DynamicDataSource 的类,它实现了 DataSource 接口,这个接口的泛型参数指定为 DynamicFlatNode,表示这个数据源提供的是 DynamicFlatNode 类型的数据。

dataChange = new BehaviorSubject([]); //定义了一个名为 dataChange 的 BehaviorSubject 对象,用于在数据发生变化时向订阅者(subscribers)发送通

get data(): DynamicFlatNode[] {

//定义了一个名为 data 的 getter 方法,用于获取树形结构的数据。在这个方法中,通过访问 dataChange 的值来获取当前的树形结构数据。

return this.dataChange.value;

}

set data(value: DynamicFlatNode[]) {

//定义了一个名为 data 的 setter 方法,用于设置树形结构的数据。在这个方法中,首先将传入的新数据赋值给 _treeControl.dataNodes 属性,然后通过调用 dataChange.next() 方法发射新的数据,以通知所有订阅者更新数据。

this._treeControl.dataNodes = value;

this.dataChange.next(value);

}

toExpired = new DynamicFlatNode('');

constructor(

// 定义了一个构造函数,接受两个参数 _treeControl 和 _database,分别表示 TreeControl 对象和数据源对象。在这个构造函数中,将接收到的 _treeControl 对象赋值给组件的 _treeControl 属性,将接收到的 _database 对象赋值给私有属性 _database。

private _treeControl: FlatTreeControl,

public treeService: TreeService

) {}

initData() {

this.treeService.getTree().subscribe((res) => {

this.data = (res || []).map(

(item: any) => new DynamicFlatNode(item.title, 0, true)

);

});

}

// 实现 DataSource 接口中的 connect() 和 disconnect() 方法

connect(collectionViewer: CollectionViewer): Observable {

// DataSource 接口中的 connect() 方法,这个方法返回一个 Observable 对象,它用于向 TreeControl 提供数据。

this._treeControl.expansionModel.changed.subscribe((change) => {

if (

(change as SelectionChange).added ||

(change as SelectionChange).removed

) {

this.handleTreeControl(change as SelectionChange);

}

});

return merge(collectionViewer.viewChange, this.dataChange).pipe(

map(() => this.data)

);

}

disconnect(collectionViewer: CollectionViewer): void {} // DataSource 接口中的 disconnect() 方法,这个方法在数据源不再需要使用时被调用,可以在这个方法中清理资源。

handleTreeControl(change: SelectionChange) {

// Tree中的一个方法,用于处理树形结构的控制变化

if (change.added) {

change.added.forEach((node) => this.toggleNode(node, true));

}

if (change.removed) {

change.removed

.slice()

.reverse()

.forEach((node) => this.toggleNode(node, false));

}

}

toggleNode(node: DynamicFlatNode, expand: boolean) {

// Tree中的一个方法,用于展开或关闭树形结构中的一个节点

const index = this.data.indexOf(node); //JavaScript 中数组的原生方法之一。这个方法用于返回数组中指定元素的索引,如果数组中不存在该元素,则返回 -1。

if (index < 0) {

// If no children, or cannot find the node, no op

return;

}

if (expand) {

// 根据 expand 参数的值来判断是展开节点还是折叠节点

node.isLoading = true;

this.treeService.getTree().subscribe((data) => {

const nodes = data.map(

(item: any) =>

new DynamicFlatNode(

item.title, //该节点的名字

node.level + 1, //级别

true //是否可以展开

) //通过 map 方法将子节点的名称转换为 DynamicFlatNode 对象,然后插入到当前节点的子节点列表中。

);

this.data.splice(index + 1, 0, ...nodes);

this.dataChange.next(this.data); //发出数据变化的通知,更新数据源

node.isLoading = false;

});

} else {

console.log('not expand');

let count = 0;

for (

let i = index + 1;

i < this.data.length && this.data[i].level > node.level;

i++, count++

) {}

this.data.splice(index + 1, count);

}

this.dataChange.next(this.data); //发出数据变化的通知,更新数据源

}

//删除节点

remove(node: DynamicFlatNode) {

const index = this.data.indexOf(node); JavaScript 中数组的原生方法之一。这个方法用于返回数组中指定元素的索引,如果数组中不存在该元素,则返回 -1。

if (index < 0) return;

let count = 1;

// 计算从该节点开始,需要删除的子节点个数 为什么是循环?因为data已经被打平了

for (

let i = index + 1;

i < this.data.length && this.data[i].level > node.level; //循环条件,当满足两个条件时继续循环:1:变量 i 不超过数组 this.data 的长度; 2:this.data[i].level > node.level说明该节点是被删除节点的子节点,需要被一并删除

i++, count++ // 每次循环结束后 做的操作

) {}

this.data.splice(index, count);

this.dataChange.next(this.data);

}

// 编辑节点

update(node: DynamicFlatNode, name: string) {

const index = this.data.indexOf(node); // 这行代码用于查找要更新的节点在数据源中的索引位置。

console.log('index: ', index);

if (index < 0) return;

this.data.splice(

index,

1,

new DynamicFlatNode(

name,

node.level,

node.expandable,

node.isLoading,

node.isChosen

)

);

this.dataChange.next(this.data);

}

// 新增节点

insert(parentNode: DynamicFlatNode, nodeName: string) {

if (!nodeName) return;

const index = this.data.indexOf(parentNode);

if (index < 0) return;

const child = new DynamicFlatNode(nodeName, parentNode.level + 1, true);

this.data.splice(index + 1, 0, child);

this.dataChange.next(this.data);

}

// 选择节点

choseNode(node: DynamicFlatNode) {

const index = this.data.indexOf(node);

if (index < 0) return;

this.data.forEach((item) => {

if (item.isChosen === true) {

item.isChosen = false;

return; // 终止循环

}

});

this.data.splice(

index,

1,

new DynamicFlatNode(

node.item,

node.level,

node.expandable,

node.isLoading,

true

)

);

this.dataChange.next(this.data);

}

}

@Component({

selector: 'app-dynamic-tree-demo',

templateUrl: './dynamic-tree-demo.component.html',

styleUrls: ['./dynamic-tree-demo.component.scss'],

}) //定义组件的装饰器要 紧挨着组件ts 要不要报错

export class DynamicTreeDemoComponent implements OnInit {

constructor(public treeService: TreeService) {

this.treeControl = new FlatTreeControl(

this.getLevel,

this.isExpandable

);

this.dataSource = new DynamicDataSource(this.treeControl, this.treeService);

this.dataSource.initData(); //获得初始数据

}

treeControl: FlatTreeControl; //表示树形结构的控制器对象,用于管理树形结构的展开和闭合。

dataSource: DynamicDataSource; //表示树形结构的数据源对象,用于管理树形结构的数据加载。

getLevel = (node: DynamicFlatNode) => node.level; //一个箭头函数,用于获取节点的级别。

isExpandable = (node: DynamicFlatNode) => node.expandable; //箭头函数,用于判断节点是否可展开。

delNode(node: any) {

this.dataSource.remove(node);

}

editNode(node: any) {

this.dataSource.update(node, '改变名字');

}

addNode(e: any) {

this.dataSource.insert(e[0], e[1]);

}

// 选择节点

choseNode(node: DynamicFlatNode) {

this.dataSource.choseNode(node);

}

hasChild = (_: number, _nodeData: DynamicFlatNode) => _nodeData.expandable; //函数,用于判断节点是否有子节点。

ngOnInit(): void {}

}

scss // 树节点的高度

:host ::ng-deep .mat-tree-node {

max-height: 30px;

min-height: 30px;

border-radius: 3px;

}

// 树节点高亮

.node--highlight {

background: #ecf2fe;

color: #006eff;

}

// 自定义row宽度

.hover-row {

width: 100%;

}

精彩文章

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。