How to integrate Angular's material drag and drop with virtual scrolling?
I was able to get drag and drop working inside of virtual scroll with Angular 8.
<cdk-virtual-scroll-viewport itemSize="10" class="viewport">
<mat-chip-list
class="mat-chip-list-stacked"
cdkDropList
[cdkDropListData]="items"
(cdkDropListDropped)="drop($event)">
<mat-chip *cdkVirtualFor="let item of items" cdkDrag>
{{ item.name }}
</mat-chip>
</mat-chip-list>
</cdk-virtual-scroll-viewport>
For some reason, moveItemInArray
did not fire off change detection in the *cdkVirtualFor
like it did for *ngFor
. So, I added this.auditItems = [...this.auditItems];
to my drop event and that seemed to fix it.
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
this.items = [...this.items];
}
So the thing is, the drop list is referencing the indexes of the rendered items. So this would only work if you add the start index of the currently rendered view to the dropped item from/to indexes, like so:
<cdk-virtual-scroll-viewport cdkDropList #virtualScroller
(cdkDropListDropped)="onItemDrop($event)" itemSize="50" class="example-viewport">
<div cdkDrag *cdkVirtualFor="let item of items" class="example-item">{{item}}</div>
</cdk-virtual-scroll-viewport>
And the typescript code would have:
@ViewChild('virtualScroller') virtualScroller: CdkVirtualScrollViewport;
...
onItemDrop(event: CdkDragDrop<FormViewOrderingItem>) {
const vsStartIndex = this.virtualScroller.getRenderedRange().start;
moveItemInArray(this.formViewOrdering, event.previousIndex + vsStartIndex, event.currentIndex + vsStartIndex);
}
For example, the event fired on drop when you move the item at index 10 to index 12 of the list when the rendered range is 2-20 will show {start: 8, end: 10}
so when you add the rendered start index, it fixes the problem.
Hope this helps, it worked for me.