You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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[];
}

问题分析

  1. 重复的容器ID:idList和idListSub在循环中重复使用,导致每个Level1/Level2的dropList容器ID不唯一,cdkDropListConnectedTo无法正确识别目标容器。
  2. 错误的连接关系:Level3的dropList仅连接当前父级的Level2容器,无法和其他Level1下的Level2容器建立连接,导致跨Level1拖拽Level3元素时没有目标容器。
  3. 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;
  }
}

关键修改说明

  1. 唯一容器ID:为每个Level1/Level2容器生成唯一ID(如level1-1、level2-11),避免ID冲突。
  2. 跨层级连接:通过getAllLevel2Ids()方法获取所有Level2容器的ID,让Level3的dropList连接所有Level2容器,实现跨Level1拖拽Level3元素到任意Level2下。
  3. 修正cdkDragData:将拖拽元素的cdkDragData绑定为当前项(level1/level2/level3),符合拖拽数据传递的最佳实践。
  4. 优化cdkDropListGroup:仅在最外层添加cdkDropListGroup,避免内层重复定义导致的分组混乱。

内容的提问来源于stack exchange,提问作者dna

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 16:55:15