自存放忘 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;
      }
    
相关推荐
菜根Sec23 分钟前
XSS跨站脚本攻击漏洞练习
前端·xss
m0_7482571830 分钟前
Spring Boot FileUpLoad and Interceptor(文件上传和拦截器,Web入门知识)
前端·spring boot·后端
桃园码工1 小时前
15_HTML5 表单属性 --[HTML5 API 学习之旅]
前端·html5·表单属性
百万蹄蹄向前冲1 小时前
2024不一样的VUE3期末考查
前端·javascript·程序员
轻口味2 小时前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami2 小时前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
wakangda2 小时前
React Native 集成原生Android功能
javascript·react native·react.js
吃杠碰小鸡2 小时前
lodash常用函数
前端·javascript
emoji1111113 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼3 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs