I'm trying to integrate Angular material's virtual scrolling with drag and drop, but for some reason when i'm trying to implement this it reverts the items and when i'm trying to drag and drop an element it doesn't work.
Here is a summary of the code
<cdk-virtual-scroll-viewport cdkDropList itemSize="50" class="example-viewport">
<div cdkDrag *cdkVirtualFor="let item of items" class="example-item">{{item}}</div>
</cdk-virtual-scroll-viewport>
As you can see, I didn't do anything special, besides that I replaced *ngFor
with cdkVirtualFor
because the docs are telling me that:
*cdkVirtualFor
replaces*ngFor
inside of a<cdk-virtual-scroll-viewport>
, supporting the exact same API as*ngFor
.
I've attached here a stackblitz demo! So, how to integrate drag and drop with virtual scrolling?
The @angular/cdk/drag-drop module provides you with a way to easily and declaratively create drag-and-drop interfaces, with support for free dragging, sorting within a list, transferring items between lists, animations, touch devices, custom drag handles, previews, and placeholders, in addition to horizontal lists and ...
The new feature of Angular 7 Virtual Scrolling takes care of loading the elements which are visible to the user. As the user scrolls, the next list of dom elements visible to user is displayed. This gives faster experience and the scrolling is also very smooth.
[itemSize] dictates how tall in pixels each row in the list is. The virtual scroller then uses this (in part) to determine how many rows it can buffer above and below the viewport. The less tall you make the itemSize , the more it will try to load and buffer.
*cdkVirtualFor accepts data from an Array , Observable<Array> , or DataSource . The DataSource for the virtual scroll is the same one used by the table and tree components. A DataSource is simply an abstract class that has two methods: connect and disconnect .
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With