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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 19:47:25