Angular不同组件及Mat手风琴内CDK拖拽列表互联问题
解决Angular CDK跨独立组件拖拽(手风琴内)的问题
问题背景
需实现Ezei与Skurt两个独立组件中Angular CDK DropList的跨组件拖拽交互,已配置cdkDropListConnectedTo属性并尝试过ViewChildren但未生效,组件放置在Angular Material手风琴中,目标是实现列表间的拖拽项交换。
现有代码核心问题
- 重复ID冲突:Ezei组件内通过
*ngFor循环生成多个cdkDropList,但所有列表都使用固定IDeven,重复ID会导致CDK无法正确识别连接目标 - 跨组件ID连接不可靠:独立组件内部的DropList ID在跨组件场景下,尤其是手风琴这种动态DOM容器中,CDK的ID查找机制容易失效
- 未利用输入属性:组件定义的
@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
相关产品推荐
相关产品推荐

