实现两个Angular Material树形视图列表间的拖拽功能求助
实现Angular Material嵌套树形视图跨列表拖拽
核心思路
借助Angular CDK的cdkDropListConnectedTo属性建立两个树形容器的连接,同时在拖拽处理逻辑中区分同容器嵌套拖拽和跨容器拖拽场景,分别处理节点的移动/转移操作。
步骤1:模板改造(两个树形容器)
给每个mat-tree的外层容器添加cdkDropList,通过模板引用变量互相指定connectedTo,确保两个列表能互相接收拖拽元素:
<!-- 第一个树形列表 --> <div cdkDropList #treeList1="cdkDropList" [cdkDropListConnectedTo]="[treeList2]" (cdkDropListDropped)="drop($event, treeData1)" > <mat-tree [dataSource]="dataSource1" [treeControl]="treeControl1" cdkDrag> <!-- 基础节点模板 --> <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle> <li class="mat-tree-node"> <div cdkDragHandle>{{node.name}}</div> </li> </mat-tree-node> <!-- 嵌套节点模板 --> <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild"> <li> <div class="mat-tree-node"> <button mat-icon-button matTreeNodeToggle> <mat-icon>{{treeControl1.isExpanded(node) ? 'expand_more' : 'chevron_right'}}</mat-icon> </button> <div cdkDragHandle>{{node.name}}</div> </div> <ul [class.mat-tree-invisible]="!treeControl1.isExpanded(node)"> <ng-container matTreeNodeOutlet></ng-container> </ul> </li> </mat-nested-tree-node> </mat-tree> </div> <!-- 第二个树形列表 --> <div cdkDropList #treeList2="cdkDropList" [cdkDropListConnectedTo]="[treeList1]" (cdkDropListDropped)="drop($event, treeData2)" > <mat-tree [dataSource]="dataSource2" [treeControl]="treeControl2" cdkDrag> <!-- 节点模板和第一个列表完全一致 --> <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle> <li class="mat-tree-node"> <div cdkDragHandle>{{node.name}}</div> </li> </mat-tree-node> <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild"> <li> <div class="mat-tree-node"> <button mat-icon-button matTreeNodeToggle> <mat-icon>{{treeControl2.isExpanded(node) ? 'expand_more' : 'chevron_right'}}</mat-icon> </button> <div cdkDragHandle>{{node.name}}</div> </div> <ul [class.mat-tree-invisible]="!treeControl2.isExpanded(node)"> <ng-container matTreeNodeOutlet></ng-container> </ul> </li> </mat-nested-tree-node> </mat-tree> </div>
步骤2:组件逻辑处理
定义两个树的数据源和控制器,在drop方法中区分两种拖拽场景:
import { Component } from '@angular/core'; import { NestedTreeControl } from '@angular/cdk/tree'; import { MatTreeNestedDataSource } from '@angular/material/tree'; import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; // 保持和原示例一致的节点结构 interface TreeNode { name: string; children?: TreeNode[]; } @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { // 第一个树的控制器和数据源 treeControl1 = new NestedTreeControl<TreeNode>(node => node.children); dataSource1 = new MatTreeNestedDataSource<TreeNode>(); treeData1: TreeNode[] = [ { name: 'Fruit', children: [{name: 'Apple'}, {name: 'Banana'}, {name: 'Fruit loops'}] }, {name: 'Drink', children: [{name: 'Water'}, {name: 'Soda'}]} ]; // 第二个树的控制器和数据源 treeControl2 = new NestedTreeControl<TreeNode>(node => node.children); dataSource2 = new MatTreeNestedDataSource<TreeNode>(); treeData2: TreeNode[] = [ { name: 'Vegetables', children: [{name: 'Broccoli'}, {name: 'Carrot'}, {name: 'Spinach'}] }, {name: 'Meat', children: [{name: 'Beef'}, {name: 'Chicken'}]} ]; ngOnInit() { this.dataSource1.data = this.treeData1; this.dataSource2.data = this.treeData2; } hasChild = (_: number, node: TreeNode) => !!node.children && node.children.length > 0; drop(event: CdkDragDrop<TreeNode[]>, targetTreeData: TreeNode[]) { // 同容器内拖拽:复用原嵌套拖拽逻辑 if (event.previousContainer === event.container) { // 这里可直接复用你原示例中处理嵌套节点移动的逻辑 // 示例(根节点层级移动): moveItemInArray(targetTreeData, event.previousIndex, event.currentIndex); } else { // 跨容器拖拽:分两种情况处理 if (event.currentIndex < targetTreeData.length) { // 拖到目标树的根节点层级 transferArrayItem( event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex ); } else { // 拖到目标树的某个子节点下(需结合嵌套逻辑定位父节点) const draggedNode = event.previousContainer.data[event.previousIndex]; // 示例:假设拖到最后一个根节点的子列表中,实际需根据拖拽位置动态定位父节点 const targetParent = targetTreeData[targetTreeData.length - 1]; if (!targetParent.children) targetParent.children = []; targetParent.children.push(draggedNode); // 从原容器移除节点 event.previousContainer.data.splice(event.previousIndex, 1); } } // 更新数据源引用触发视图刷新 this.dataSource1.data = [...this.treeData1]; this.dataSource2.data = [...this.treeData2]; } }
关键注意事项
- 拖拽手柄:确保
cdkDragHandle绑定在节点的可操作区域,避免和树的展开/折叠按钮冲突。 - 嵌套节点跨容器拖拽:如果需要支持拖到目标树的子节点下,需结合原示例的节点路径定位逻辑,找到对应的父节点后再添加子节点。
- 数据源更新:每次拖拽后必须创建新的数组引用(如
[...this.treeData1]),否则Angular的变更检测不会触发树视图更新。
内容的提问来源于stack exchange,提问作者Lukas
相关产品推荐
相关产品推荐

