自存放忘 angular 动态加载子节点

  1. html

    复制代码
    <mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
      <mat-tree-node
        *matTreeNodeDef="let node"
        matTreeNodePadding
        [matTreeNodePaddingIndent]="10"
        (click)="choseNode(node)"
        [ngClass]="{ 'node--highlight': node.isChosen }"
      >
      <!-- 此为自定义的样式 组件;不用改成{{node.name}} 就好-->
         <app-custom-hover-node
          [node]="node"
          class="hover-row"
          (delNode)="delNode($event)"
          (editNode)="editNode($event)"
          (addNode)="addNode($event)"
        ></app-custom-hover-node>
      </mat-tree-node>
      <mat-tree-node
        *matTreeNodeDef="let node; when: hasChild"
        matTreeNodePadding
        [matTreeNodePaddingIndent]="20"
        (click)="choseNode(node)"
        [ngClass]="{ 'node--highlight': node.isChosen }"
      >
        <button
          mat-icon-button
          [attr.aria-label]="'Toggle ' + node.item"
          matTreeNodeToggle
        >
          <mat-icon class="mat-icon-rtl-mirror">
            {{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }}
          </mat-icon>
        </button>
        <app-custom-hover-node
          [node]="node"
          class="hover-row"
          (delNode)="delNode($event)"
          (editNode)="editNode($event)"
          (addNode)="addNode($event)"
        ></app-custom-hover-node>
    
        <mat-progress-bar
          *ngIf="node.isLoading"
          mode="indeterminate"
          class="example-tree-progress-bar"
        ></mat-progress-bar>
      </mat-tree-node>
    </mat-tree>
  2. 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<DynamicFlatNode> {
      // 定义了一个名为 DynamicDataSource 的类,它实现了 DataSource 接口,这个接口的泛型参数指定为 DynamicFlatNode,表示这个数据源提供的是 DynamicFlatNode 类型的数据。
      dataChange = new BehaviorSubject<DynamicFlatNode[]>([]); //定义了一个名为 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<DynamicFlatNode>,
        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<DynamicFlatNode[]> {
        // DataSource 接口中的 connect() 方法,这个方法返回一个 Observable 对象,它用于向 TreeControl 提供数据。
        this._treeControl.expansionModel.changed.subscribe((change) => {
          if (
            (change as SelectionChange<DynamicFlatNode>).added ||
            (change as SelectionChange<DynamicFlatNode>).removed
          ) {
            this.handleTreeControl(change as SelectionChange<DynamicFlatNode>);
          }
        });
    
        return merge(collectionViewer.viewChange, this.dataChange).pipe(
          map(() => this.data)
        );
      }
      disconnect(collectionViewer: CollectionViewer): void {} // DataSource 接口中的 disconnect() 方法,这个方法在数据源不再需要使用时被调用,可以在这个方法中清理资源。
    
      handleTreeControl(change: SelectionChange<DynamicFlatNode>) {
        // 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<DynamicFlatNode>(
          this.getLevel,
          this.isExpandable
        );
        this.dataSource = new DynamicDataSource(this.treeControl, this.treeService);
        this.dataSource.initData(); //获得初始数据
      }
      treeControl: FlatTreeControl<DynamicFlatNode>; //表示树形结构的控制器对象,用于管理树形结构的展开和闭合。
    
      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 {}
    }
  3. 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%;
      }
      .custome-btn {
        line-height: 30px;
        height: 30px;
        width: 30px;
      }
      // 搜索框
      :host ::ng-deep .mat-form-field{
        width: 100%;
      }
      :host ::ng-deep .mat-form-field-appearance-outline .mat-form-field-outline{
        background: #F2F3F5;
      }
      :host ::ng-deep .mat-form-field-wrapper{
        padding-bottom: 0.625rem;
      }
      :host ::ng-deep .mat-form-field-appearance-outline .mat-form-field-flex{
        margin-top: -1rem;
        height: 40px;
      }
      :host ::ng-deep .mat-form-field-infix{
        border-top:8px solid transparent;
      }
相关推荐
passerby606110 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了10 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅10 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅11 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅11 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment11 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅11 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊11 小时前
jwt介绍
前端
爱敲代码的小鱼11 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte12 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc