Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to integrate Angular's material drag and drop with virtual scrolling?

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?

like image 595
MTZ Avatar asked Dec 17 '18 14:12

MTZ


People also ask

Does angular support drag and drop?

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 ...

What is virtual scrolling in angular?

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.

What is itemSize in CDK virtual scroll viewport?

[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.

What is cdkVirtualFor?

*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 .


2 Answers

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];
}
like image 85
Nabel Avatar answered Sep 19 '22 18:09

Nabel


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.

like image 26
ronenmiller Avatar answered Sep 18 '22 18:09

ronenmiller