Angular Material嵌套树是否支持拖拽?如何实现拖拽功能?
Angular Material 嵌套树拖拽功能说明
核心结论
Angular Material 嵌套树(Nested Tree)无官方原生支持的拖拽方案,你此前了解的信息准确:官方更推荐使用扁平树(Flat Tree)实现拖拽,因为扁平树的DOM结构为单层级列表,仅通过内边距区分嵌套层级,和CDK拖拽模块的适配成本极低,性能更优。
嵌套树并非完全无法实现拖拽,但是需要自行编写大量自定义逻辑处理嵌套DOM结构带来的定位、层级识别、数据源更新问题,开发和维护成本远高于使用扁平树方案。
嵌套树拖拽实现示例(基于你提供的代码改造)
前置依赖
首先确保你的Angular模块中已导入DragDropModule:
import { DragDropModule } from '@angular/cdk/drag-drop'; @NgModule({ imports: [ // 其他已导入的模块 DragDropModule, MatTreeModule, MatIconModule ] })
模板改造
需要在原有嵌套树模板基础上增加拖拽相关的指令和事件处理:
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl" cdkDropList (cdkDropListDropped)="onDrop($event)"> <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild" cdkDrag [cdkDragData]="node"> <!-- 拖拽预览自定义 --> <div *cdkDragPreview class="drag-preview"> {{node.sectionTitle}} </div> <div> <div class="mat-tree-node" matTreeNodeToggle (contextmenu)="selectednode = node;onRightClick($event);" [ngClass]="{ 'background-highlight':selectednode === node }"> <mat-icon class="folder-icon-spacing" fontSet="far" [fontIcon]="treeControl.isExpanded(node) ? 'fa-folder-open' : 'fa-folder'"> </mat-icon> <mat-icon class="mat-icon-rtl-mirror chevron-color"> {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}} </mat-icon> <div class="node-format item-font"> {{node.sectionTitle}}</div> <div class="align-setproperties item-font" (click)="showProperties(node)"> <span *ngIf="node.sectionOrder !== 'Select one';else showprop"> <span *ngIf="node.randomCount === 0 || node.randomCount === '' || node.randomCount == null || node.hasTitleError || node.hasRandomError ; else validCount"> <span *ngIf="node.hasTitleError && !node.hasRandomError; else invalid"> {{node.sectionOrder}} - {{node.randomCount}} of {{node.totalCount}} </span> <ng-template #invalid> <span> {{node.sectionOrder}} - ? of {{node.totalCount}} </span> </ng-template> </span> <ng-template #validCount> <span class="validText">{{node.sectionOrder}} - {{node.randomCount}} of {{node.totalCount}} </span> </ng-template> </span> <ng-template #showprop>Set properties</ng-template> </div> <div style="visibility: hidden; position: fixed" [style.left]="contextMenuPosition.x" [style.top]="contextMenuPosition.y" [matMenuTriggerFor]="contextMenu"> </div> <mat-menu #contextMenu="matMenu"> <button mat-menu-item (click)="add(selectednode)">Add</button> <button mat-menu-item *ngIf="selectednode && selectednode.sectionIdent === 0 ? false: true" (click)="removeNode(selectednode)">Remove</button> </mat-menu> </div> </div> <div [class.example-tree-invisible]="!treeControl.isExpanded(node)" role="group" cdkDropList [cdkDropListData]="node.children" (cdkDropListDropped)="onDrop($event, node)"> <ng-container matTreeNodeOutlet> </ng-container> </div> <div *ngFor="let sibling of node.items" style="display:block;min-height: 10px;padding-left: 96px;"> {{sibling}} </div> </mat-nested-tree-node> <!-- 叶子节点定义,你原有代码缺失部分,需要补充 --> <mat-tree-node *matTreeNodeDef="let node" cdkDrag [cdkDragData]="node"> <div class="mat-tree-node"> <!-- 叶子节点内容和上面父节点内容保持一致即可 --> </div> </mat-tree-node> </mat-tree>
TS逻辑补充
需要实现拖拽完成后的数据源更新逻辑:
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; // 拖拽完成事件处理 onDrop(event: CdkDragDrop<any[]>, parentNode?: any) { // 同一父级下拖拽排序 if (event.previousContainer === event.container) { moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); } else { // 跨父级拖拽,转移节点 transferArrayItem( event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex ); } // 刷新数据源 this.dataSource.data = [...this.dataSource.data]; } // 原有hasChild方法保持不变 hasChild = (_: number, node: any) => !!node.children && node.children.length > 0;
注意事项
- 以上仅为最小实现样例,你还需要自行处理边界场景:比如限制根节点不能拖入子级、折叠状态下的节点放置判定、拖拽位置提示(放在目标上方/下方/作为子节点)等
- 嵌套树拖拽的性能远低于扁平树,当节点数量较多时会出现明显卡顿,若非业务必须使用嵌套树,优先切换为扁平树实现拖拽功能
内容的提问来源于stack exchange,提问作者Ajay K
相关产品推荐
相关产品推荐

