Angular Material拖拽异常:拖拽正常但无法正确放置
Angular Material拖拽放置后回退原区域的问题修复
问题现象
使用Angular Material的drag-and-drop功能时,任务卡片可正常拖拽,但放入目标放置区后,卡片会立刻回到原区域,无法完成跨区域转移。
核心错误原因
- 临时数组导致数据未持久化:
assignTasksByStatus方法每次调用都会返回一个新的过滤数组,cdkDropListData绑定的是这个临时数组,transferArrayItem操作的只是临时数组,而非原始的tasks数组。组件变更检测时,临时数组被丢弃,原始数据未更新,视图自然回退。 - 未更新任务的状态属性:即使临时数组被修改,也没有同步更新任务的
status字段,导致下次过滤时任务仍会出现在原状态分组中。 - 子组件数据独立:每个
app-task-status组件都持有自己的tasks数组,拖拽操作只影响当前组件的临时数据,其他组件的数据未同步。
修复方案及代码修改
步骤1:将共享数据提升到父组件
把tasks数组移到task-status-list.component.ts中,通过输入传递给子组件,确保所有状态分组共享同一数据源:
// task-status-list.component.ts export class TaskStatusListComponent { protected readonly TASK_STATUSES = TASK_STATUSES; tasks: Task[] = [ { id: 1, title: 'Task 1', status: TASK_STATUSES['TO_DO'] }, { id: 2, title: 'Task 2', status: TASK_STATUSES['TO_DO'] }, { id: 3, title: 'Task 3', status: TASK_STATUSES['DONE'] }, ]; onDrop(event: CdkDragDrop<Task[]>, targetStatus: TaskStatus) { 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 ); // 更新任务的状态属性 const movedTask = event.container.data[event.currentIndex]; movedTask.status = targetStatus; } } }
步骤2:修改父组件模板,传递数据和回调
<!-- task-status-list.component.html --> <div cdkDropListGroup class="task-status-list-container"> @for (taskStatus of Object.values(TASK_STATUSES); track taskStatus.key) { <app-task-status [taskStatus]="taskStatus" [tasks]="tasks" (drop)="onDrop($event, taskStatus)" ></app-task-status> } </div>
步骤3:修改子组件,绑定原始数组并过滤显示
子组件不再持有独立数据,直接使用父组件传递的tasks,并在模板中过滤当前状态的任务:
// task-status.component.ts export class TaskStatusComponent { @Input() taskStatus!: TaskStatus; @Input() tasks!: Task[]; @Output() drop = new EventEmitter<CdkDragDrop<Task[]>>(); // 过滤当前状态的任务,供模板使用 get currentStatusTasks(): Task[] { return this.tasks.filter(task => task.status.key === this.taskStatus.key); } handleDrop(event: CdkDragDrop<Task[]>) { this.drop.emit(event); } }
<!-- task-status.component.html --> <div class="task-status-container"> <div class="title-container"> <div class="title">{{ taskStatus.value }}</div> </div> <div cdkDropList [cdkDropListData]="tasks" <!-- 绑定原始tasks数组 --> (cdkDropListDropped)="handleDrop($event)" class="task-status-container-body" > @for (task of currentStatusTasks; track task.id) { <app-task cdkDrag [task]="task"></app-task> } </div> </div>
关键说明
- 绑定
cdkDropListData时必须使用原始数据源数组,不能用每次都返回新数组的过滤方法,否则transferArrayItem无法修改真实数据。 - 拖拽完成后必须更新任务的
status属性,确保过滤逻辑能正确识别任务的新分组。 - 共享数据源到父组件,保证所有子组件的数据一致性,避免独立数据导致的不同步问题。
内容的提问来源于stack exchange,提问作者coder
相关产品推荐
相关产品推荐

