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

Angular不同组件及Mat手风琴内CDK拖拽列表互联问题

解决Angular CDK跨独立组件拖拽(手风琴内)的问题

问题背景

需实现Ezei与Skurt两个独立组件中Angular CDK DropList的跨组件拖拽交互,已配置cdkDropListConnectedTo属性并尝试过ViewChildren但未生效,组件放置在Angular Material手风琴中,目标是实现列表间的拖拽项交换。

现有代码核心问题

  1. 重复ID冲突:Ezei组件内通过*ngFor循环生成多个cdkDropList,但所有列表都使用固定IDeven,重复ID会导致CDK无法正确识别连接目标
  2. 跨组件ID连接不可靠:独立组件内部的DropList ID在跨组件场景下,尤其是手风琴这种动态DOM容器中,CDK的ID查找机制容易失效
  3. 未利用输入属性:组件定义的@Input() connectedList未实际使用,硬编码的连接目标无法适配动态场景

修复方案

1. 生成唯一DropList ID

给每个动态生成的DropList分配唯一ID,避免冲突

2. 通过父组件传递连接引用

使用模板引用获取子组件内的DropList实例,通过@Input传递给目标组件,替代ID连接方式

3. 优化手风琴拖拽体验(可选)

拖拽时自动展开目标面板,避免因面板折叠导致无法拖拽


修改后的完整代码

父组件(App Component)

HTML模板

<mat-accordion>
  <mat-expansion-panel #forLaterPanel [expanded]="openForLater" 
    (closed)="closePanel('for-later')">
    <mat-expansion-panel-header>
      <mat-panel-title>Pour plus tard</mat-panel-title>
      <mat-panel-description>Messages ajoutés lors de cette session</mat-panel-description>
    </mat-expansion-panel-header>    
    <app-ezei [connectedDropLists]="[skurtDropList]"></app-ezei>
  </mat-expansion-panel>

  <mat-expansion-panel #preBroadcastPanel [expanded]="openPreBroadcast" 
    (closed)="closePanel('pre-broadcast')">
    <mat-expansion-panel-header>
      <mat-panel-title>Liste de pré-diffusion</mat-panel-title>
    </mat-expansion-panel-header>
    <app-skurt #skurtComponent [connectedDropLists]="ezeiDropLists"></app-skurt>
  </mat-expansion-panel>
</mat-accordion>

TS文件

import { Component, ViewChild, ViewChildren, QueryList } from '@angular/core';
import { MatExpansionPanel } from '@angular/material/expansion';
import { EzeiComponent } from './ezei/ezei.component';
import { SkurtComponent } from './skurt/skurt.component';
import { CdkDropList } from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  openForLater = true;
  openPreBroadcast = true;

  @ViewChild(EzeiComponent) ezeiComponent!: EzeiComponent;
  @ViewChild(SkurtComponent) skurtComponent!: SkurtComponent;
  @ViewChild('forLaterPanel') forLaterPanel!: MatExpansionPanel;
  @ViewChild('preBroadcastPanel') preBroadcastPanel!: MatExpansionPanel;

  get ezeiDropLists(): QueryList<CdkDropList> {
    return this.ezeiComponent?.dropLists || new QueryList<CdkDropList>();
  }

  get skurtDropList(): CdkDropList {
    return this.skurtComponent?.dropList || null;
  }

  closePanel(panel: string) {
    // 面板关闭逻辑
  }

  // 可选:拖拽时展开目标面板
  handleDragStarted(targetPanel: MatExpansionPanel) {
    if (!targetPanel.expanded) {
      targetPanel.expanded = true;
    }
  }
}

Ezei组件

TS文件

import { Component, Input, ViewChildren, QueryList } from '@angular/core';
import { All } from '../all.interface';
import { CdkDragDrop, DragDropModule, moveItemInArray, transferArrayItem, CdkDropList } from '@angular/cdk/drag-drop';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-ezei',
  standalone: true,
  imports: [CommonModule, DragDropModule],
  templateUrl: './ezei.component.html',
  styleUrl: './ezei.component.css'
})
export class EzeiComponent {
  @Input() connectedDropLists!: CdkDropList[];

  @ViewChildren(CdkDropList) dropLists!: QueryList<CdkDropList>;

  all: All[] = [
    { id: 2, teletubies: ['rouge', 'jaune', 'vert', 'violet'] },
    { id: 3, teletubies: ['bleu', 'orange', 'rose', 'gris'] },
    { id: 4, teletubies: ['noir', 'blanc', 'marron', 'cyan'] }
  ];
  
  even = [
    { id: 10, teletubies: [] }, 
    { id: 11, teletubies: [] }, 
    { id: 12, teletubies: [] }
  ];

  dropTeletubies(event: CdkDragDrop<string[]>) {
    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
      );
    }
  }
}

HTML模板

<div class="example-container">
  <h2>Even numbers</h2>
  <div>
    <div class="example-box" *ngFor="let number of all">
      <div>{{number.id}}</div>
      <div
        cdkDropList
        [id]="'even-' + number.id"
        [cdkDropListConnectedTo]="connectedDropLists"
        [cdkDropListData]="number.teletubies"
        class="example-list teletubies-list"
        (cdkDropListDropped)="dropTeletubies($event)">
        <div
          *ngFor="let tele of number.teletubies"
          cdkDrag
          [cdkDragData]="tele"
          class="example-box">{{tele}}</div>
      </div>
    </div>
  </div>
</div>

Skurt组件

TS文件

import { CdkDragDrop, DragDropModule, moveItemInArray, transferArrayItem, CdkDropList } from '@angular/cdk/drag-drop';
import { CommonModule } from '@angular/common';
import { Component, Input, ViewChild } from '@angular/core';

@Component({
  selector: 'app-skurt',
  standalone: true,
  imports: [CommonModule, DragDropModule],
  templateUrl: './skurt.component.html',
  styleUrl: './skurt.component.css'
})
export class SkurtComponent {
  @Input() connectedDropLists!: CdkDropList[];

  @ViewChild(CdkDropList) dropList!: CdkDropList;

  teletubies = ['rouge', 'jaune', 'vert', 'violet'];

  drop(event: CdkDragDrop<string[]>) {
    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
      );
    }
  }
}

HTML模板

<div class="example-container">
  <h2>Available numbers</h2>
  <div
    cdkDropList
    id="all"
    [cdkDropListConnectedTo]="connectedDropLists"
    [cdkDropListData]="teletubies"
    (cdkDropListDropped)="drop($event)">
    <div
      class="example-box"
      *ngFor="let number of teletubies"
      [cdkDragData]="number"
      cdkDrag>{{number}}</div>
  </div>
</div>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 10:45:02