Angular嵌套三级列表拖拽问题:跨Level1的Level3元素无法移入Level2
Angular CDK Drag&Drop三级嵌套列表拖拽问题解决
问题描述
基于Angular Drag & Drop实现三级嵌套列表时,无法将Level3元素拖拽到另一个Level1元素下的Level2元素中。
原实现代码
HTML代码
<div class="form-wizard" cdkDropListGroup cdkDropList [cdkDropListData]="datas" (cdkDropListDropped)="dropItem($event)"> <mat-list> <div [id]="idList" cdkDrag [cdkDragData]="datas" *ngFor="let level1 of datas" class="chapitre-expander"> <mat-list-item> {{level1.id}} - {{ level1.title }}</mat-list-item> <mat-list cdkDropListGroup cdkDropList [cdkDropListData]="level1.children" [cdkDropListConnectedTo]="idList" (cdkDropListDropped)="dropItem($event)"> <div [id]="idListSub" cdkDrag [cdkDragData]="level1.children" *ngFor="let level2 of level1.children" class="sous-chapitre-expander"> <mat-list-item> {{level2.id}} - {{ level2.title }} </mat-list-item> <mat-list cdkDropList [cdkDropListData]="level2.children" [cdkDropListConnectedTo]="idListSub" (cdkDropListDropped)="dropItem($event)"> <div cdkDrag [cdkDragData]="level2.children" *ngFor="let level3 of level2.children" class="question-expander"> <mat-list-item> {{level3.id}} - {{ level3.title }} </mat-list-item> </div> </mat-list> </div> </mat-list> </div> </mat-list> </div>
拖拽处理方法
dropItem(event: any) { if (event.container === event.previousContainer) { moveItemInArray( event.container.data, event.previousIndex, event.currentIndex ); } else { transferArrayItem( event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex ); } }
数据源
datas: Item[] = [ { id: '1', title: 'item 1', showChildren: false, children: [ { id: '11', title: 'Sous item 1', showChildren: false, children: [ { id: '111', title: 'sous sous item 1', showChildren: false, children: [] } ] }, { id: '21', title: 'sous item 2', showChildren: false, children: [] }, ] }, { id: '2', title: 'item 2', showChildren: false, children: [ { id: '21', title: 'Sous item 1', showChildren: false, children: [ { id: '211', title: 'sous sou item 1', showChildren: false, children: [] }, { id: '212', title: 'sous sous item 2', showChildren: false, children: [] }, { id: '213', title: 'sous sou item 3', showChildren: false, children: [] } ] }, { id: '22', title: 'sous item 2', showChildren: false, children: [] }, ] }, ]; interface Item { id: string; title: string; showChildren: boolean; children: Item[]; }
问题分析
- 重复的容器ID:
idList和idListSub在循环中重复使用,导致每个Level1/Level2的dropList容器ID不唯一,cdkDropListConnectedTo无法正确识别目标容器。 - 错误的连接关系:Level3的dropList仅连接当前父级的Level2容器,无法和其他Level1下的Level2容器建立连接,导致跨Level1拖拽Level3元素时没有目标容器。
- cdkDragData绑定错误:拖拽元素的
cdkDragData绑定的是数组而非当前项本身,虽不影响拖拽功能,但不符合最佳实践。
修正后的代码
HTML代码
<div class="form-wizard" cdkDropListGroup> <mat-list cdkDropList [cdkDropListData]="datas" (cdkDropListDropped)="dropItem($event)"> <div [id]="'level1-' + level1.id" cdkDrag [cdkDragData]="level1" *ngFor="let level1 of datas" class="chapitre-expander" > <mat-list-item> {{level1.id}} - {{ level1.title }}</mat-list-item> <mat-list cdkDropList [cdkDropListData]="level1.children" [cdkDropListConnectedTo]="['level1-' + level1.id, ...getAllLevel2Ids()]" (cdkDropListDropped)="dropItem($event)" > <div [id]="'level2-' + level2.id" cdkDrag [cdkDragData]="level2" *ngFor="let level2 of level1.children" class="sous-chapitre-expander" > <mat-list-item>{{level2.id}} - {{ level2.title }}</mat-list-item> <mat-list cdkDropList [cdkDropListData]="level2.children" [cdkDropListConnectedTo]="getAllLevel2Ids()" (cdkDropListDropped)="dropItem($event)" > <div cdkDrag [cdkDragData]="level3" *ngFor="let level3 of level2.children" class="question-expander" > <mat-list-item>{{level3.id}} - {{ level3.title }}</mat-list-item> </div> </mat-list> </div> </mat-list> </div> </mat-list> </div>
修正后的TS代码
import { Component } from '@angular/core'; import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; interface Item { id: string; title: string; showChildren: boolean; children: Item[]; } @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { datas: Item[] = [ { id: '1', title: 'item 1', showChildren: false, children: [ { id: '11', title: 'Sous item 1', showChildren: false, children: [ { id: '111', title: 'sous sous item 1', showChildren: false, children: [] } ] }, { id: '21', title: 'sous item 2', showChildren: false, children: [] }, ] }, { id: '2', title: 'item 2', showChildren: false, children: [ { id: '21', title: 'Sous item 1', showChildren: false, children: [ { id: '211', title: 'sous sou item 1', showChildren: false, children: [] }, { id: '212', title: 'sous sous item 2', showChildren: false, children: [] }, { id: '213', title: 'sous sou item 3', showChildren: false, children: [] } ] }, { id: '22', title: 'sous item 2', showChildren: false, children: [] }, ] }, ]; dropItem(event: CdkDragDrop<Item[]>) { if (event.container === event.previousContainer) { moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); } else { transferArrayItem( event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex ); } } // 获取所有Level2容器的ID,用于建立跨层级连接 getAllLevel2Ids(): string[] { const ids: string[] = []; this.datas.forEach(level1 => { level1.children.forEach(level2 => { ids.push('level2-' + level2.id); }); }); return ids; } }
关键修改说明
- 唯一容器ID:为每个Level1/Level2容器生成唯一ID(如
level1-1、level2-11),避免ID冲突。 - 跨层级连接:通过
getAllLevel2Ids()方法获取所有Level2容器的ID,让Level3的dropList连接所有Level2容器,实现跨Level1拖拽Level3元素到任意Level2下。 - 修正cdkDragData:将拖拽元素的
cdkDragData绑定为当前项(level1/level2/level3),符合拖拽数据传递的最佳实践。 - 优化cdkDropListGroup:仅在最外层添加
cdkDropListGroup,避免内层重复定义导致的分组混乱。
内容的提问来源于stack exchange,提问作者dna
相关产品推荐
相关产品推荐

