Search code examples
angularangular-materialangular-cdk-drag-drop

Angular Material Drag and Drop: Dragging an item works but not dropping


I am using Angular Material's drag-and-drop functionality in my project. While dragging works fine, I am facing issues with dropping the items. The items can be dragged, but when I try to drop them into the drop zone, then the items move to the previous zone and not to the target zone.

What am I doing wrong?

Full example: Demo @ StackBlitz

Here is a simplified version of my code:

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"></app-task-status>
  }
</div>

task-status.component.html

<div class="task-status-container">
  <div class="title-container">
    <div class="title">{{ taskStatus.value }}</div>
  </div>

  <div
    cdkDropList
    [cdkDropListData]="assignTasksByStatus(taskStatus.key)"
    (cdkDropListDropped)="drop($event)"
    class="task-status-container-body"
  >
    @for (task of assignTasksByStatus(taskStatus.key);track task.id) {
    <app-task cdkDrag [task]="task"></app-task>
    }
  </div>
</div>

task-status.component.ts

export class TaskStatusComponent {
  protected readonly TASK_STATUSES = TASK_STATUSES;
  @Input() taskStatus!: TaskStatus;
  tasksSubscription!: Subscription;
  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'] },
  ];

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

  public assignTasksByStatus(status: string): Task[] {
    return this.tasks.filter((task) => task.status.key === status);
  }
}

task.component.html

<div class="title">{{ task.title }}</div>

task.component.ts

export class TaskComponent {
  @Input() task!: Task;
}

Solution

    1. You need to import the standalone component: CdkDropListGroup in your TaskStatusListComponent.
    import { CdkDropListGroup } from '@angular/cdk/drag-drop';
    
    @Component({
      selector: 'app-task-status-list',
      standalone: true,
      imports: [TaskStatusComponent, CdkDropListGroup],
      ...
    })
    export class TaskStatusListComponent {
      ...
    }
    
    1. In your TaskStatusComponent, I would suggest that splits the tasks by status (tasksByStatus) instead of sharing the data in a single tasks array. This aims that each CdkDropList component instance has their own dataset.
    export class TaskStatusComponent {
      ...
    
      tasksByStatus!: { [key: string]: Task[] };
    
      ngOnInit() {
        this.tasksByStatus = Object.keys(TASK_STATUSES).reduce((acc, cur) => {
          acc[cur] = this.tasks.filter((x) => x.status.key === cur);
    
          return acc;
        }, {} as { [key: string]: Task[] });
      }
    
      public assignTasksByStatus(status: string): Task[] {
        return this.tasksByStatus[status];
      }
    
      ...
    }
    

    Demo @ StackBlitz