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

How to Identify Source and Target Containers in Angular Drag and Drop


I'm implementing a drag and drop feature in my Angular application using Angular Material's CDK. The drag and drop functionality works as expected, but I'm struggling with identifying the source and target containers during the drop event.

I have looked into the drop event and checked the event.previousContainer and event.container properties. However, I'm not sure how to use these properties to determine which specific containers (e.g., "TO_DO" or "DONE") an item has been moved between.

How can I modify this to accurately identify the source and target containers? Specifically, I want to determine if a task has been moved from "TO_DO" to "DONE" or vice versa. Is there a way to get the task status as well during the drop event?

Any advice or sample code would be extremely helpful.

Demo @ StackBlitz

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-statust.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'] },
  ];
  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[] });
  }

  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.tasksByStatus[status];
  }
}

task.component.html

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

task.component.ts

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

Solution

  • You can overwrite the default element id for the CdkDragDrop component by providing the task status name (TO_DO, Done, etc).

    Bonus: If you want to track which item is dropped, you can add [cdkDragData]="task" attribute.

    <div
      [id]="taskStatus.key"
      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" [cdkDragData]="task"></app-task>
      }
    </div>
    

    In the drop method, get the CdkDragDrop element ids and dropped item as below:

    drop(event: CdkDragDrop<Task[]>) {
      let fromContainer = event.previousContainer.id;
      let toContainer = event.container.id;
      let dragDropData: Task = event.item.data;
    
      if (event.previousContainer === event.container) {
        moveItemInArray(
          event.container.data,
          event.previousIndex,
          event.currentIndex
        );
      } else {
        console.log(
          `Item (${JSON.stringify(
            dragDropData
          )}) is moved from ${fromContainer} to ${toContainer}.`
        );
    
        transferArrayItem(
          event.previousContainer.data,
          event.container.data,
          event.previousIndex,
          event.currentIndex
        );
      }
    }
    

    Demo @ StackBlitz