I'm currently working on an Angular project where I need to use PrimeNG's Carousel component along with Angular Material's Drag and Drop functionality. Individually, both components work as expected. However, when I try to combine them, the drag and drop functionality stops working within the Carousel. There are no errors in the console when I attempt to drop an item.
The error occurs, when you drop Task 1 from "To Do" into the "Done" Container. Task 1 is still in "To Do" visible.
What am I doing wrong?
task-status.component.html
<div class="task-status-container">
<div class="title-container">
<div class="title">{{ taskStatus.value }}</div>
</div>
<div
cdkDropList
[id]="taskStatus.key"
[cdkDropListData]="assignTasksByStatus(taskStatus)"
(cdkDropListDropped)="drop($event)"
class="task-status-drop-list"
>
@if(getTaskCount(taskStatus) === 0) {
<div class="no-task-container">
<div>No Tasks</div>
</div>
}
<p-carousel
[value]="assignTasksByStatus(taskStatus)"
[numVisible]="1"
[numScroll]="1"
[circular]="true"
>
<ng-template let-task pTemplate="item">
<div class="border-1 surface-border border-round m-2 p-3">
<div class="mb-3">
<div class="relative mx-auto">
<app-task cdkDrag [cdkDragData]="task" [task]="task"></app-task>
</div>
</div>
</div>
</ng-template>
</p-carousel>
</div>
</div>
Below are the list of changes:
We have to provide the input [cdkDropListConnectedTo]
to CDK drag drop, so that it knows what other drag drop modules it's connected to. Here we specify this by inputing an array of IDs prepared in the root component and passed as @Input
Instead of a function, we can use a template reference of the dnd list #list="cdkDropList"
, then use the same to access the data property, which is the list, instead of using a separate function. Since the manipulation happen at the DND level, we should use the same reference in other locations.
After updating the list using moveItemInArray
or transferArrayItem
we need to create a new reference, else the change is not detected by the carousel, for this we can use array-destructuring to refresh the carousel
<div class="task-status-container">
<div class="title-container">
<div class="title">{{ taskStatus.value }}</div>
</div>
<div
#list="cdkDropList"
cdkDropList
[id]="taskStatus.key"
[cdkDropListData]="assignTasksByStatus(taskStatus)"
(cdkDropListDropped)="drop($event)"
[cdkDropListConnectedTo]="taskStatusKeys"
class="task-status-drop-list"
>
<!-- 1-->
@if(list.data.length=== 0) {
<!-- 2 -->
<div class="no-task-container">
<div>No Tasks</div>
</div>
}
<!-- 2 -->
<p-carousel
[value]="list.data"
[numVisible]="1"
[numScroll]="1"
[circular]="true"
>
<ng-template let-task pTemplate="item">
<div class="border-1 surface-border border-round m-2 p-3">
<div class="mb-3">
<div class="relative mx-auto">
<app-task cdkDrag [cdkDragData]="task" [task]="task"></app-task>
</div>
</div>
</div>
</ng-template>
</p-carousel>
</div>
</div>
import {
CdkDragDrop,
moveItemInArray,
transferArrayItem,
CdkDropList,
CdkDrag,
} from '@angular/cdk/drag-drop';
import { Component, Input, ViewChild } from '@angular/core';
import { Subscription } from 'rxjs';
import { TaskComponent } from './task/task.component';
import { TASK_STATUSES } from '../../../task-constants';
import { TaskStatus } from '../../../status';
import { Task } from '../../../task';
import { CarouselModule } from 'primeng/carousel';
@Component({
selector: 'app-task-status',
standalone: true,
imports: [TaskComponent, CdkDropList, CdkDrag, CarouselModule],
templateUrl: './task-status.component.html',
styleUrl: './task-status.component.scss',
})
export class TaskStatusComponent {
@ViewChild(CdkDropList) list!: CdkDropList;
protected readonly TASK_STATUSES = TASK_STATUSES;
@Input() taskStatus!: TaskStatus;
@Input() taskStatusKeys!: Array<string>; //<-1
tasksSubscription!: Subscription;
tasks: Task[] = [
{ id: 1, title: 'Task 1', status: TASK_STATUSES['TO_DO'] },
{ id: 2, title: 'Task 2', status: TASK_STATUSES['DONE'] },
{ 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
);
}
event.previousContainer.data = [...event.previousContainer.data]; // <= 3
event.container.data = [...event.container.data]; // <= 3
}
public assignTasksByStatus(status: TaskStatus): Task[] {
return this.tasksByStatus[status.key];
}
public getTaskCount(status: TaskStatus): number {
return this.tasksByStatus[status.key]?.length || 0;
}
}
<div cdkDropListGroup class="task-status-list-container">
@for (taskStatus of Object.values(TASK_STATUSES); track taskStatus.key) {
<app-task-status
[taskStatus]="taskStatus"
[taskStatusKeys]="taskStatusKeys"
></app-task-status>
<!-- 1-->
}
</div>
import { Component } from '@angular/core';
import { TaskStatusComponent } from './task-status/task-status.component';
import { TASK_STATUSES } from '../../task-constants';
import { CdkDropListGroup } from '@angular/cdk/drag-drop';
@Component({
selector: 'app-task-status-list',
standalone: true,
imports: [TaskStatusComponent, CdkDropListGroup],
templateUrl: './task-status-list.component.html',
styleUrl: './task-status-list.component.scss',
})
export class TaskStatusListComponent {
protected readonly TASK_STATUSES = TASK_STATUSES;
public readonly taskStatusKeys = Object.values(TASK_STATUSES).map(
//<-1
(x: any) => x.key //<-1
); //<-1
protected readonly Object = Object;
}