Using Angular with Angular Material, I've created a sample app that features drag and drop lists, one of which which uses virtual scrolling. However, with certain items, the drop event's previousIndex
value suddenly jumps to a different values. I'm baffled.
Steps to Reproduce
Drag the numbers 1 through 10 from the "To do" list to the "Done" list. When the card containing "10" is dropped, the wrong card is transferred, and previousIndex
enabling that action jumps from 0 to 1 as seen in the console log.
Code
Stackblitz is here .
HTML
<div class="whole_thing" cdkDropListGroup>
<div class="example-container">
<h2>To do (CDK virtual scroll)</h2>
<div
cdkDropList
[cdkDropListData]="todo"
class="example-list"
(cdkDropListDropped)="drop($event)">
<cdk-virtual-scroll-viewport itemSize="62" class="example-viewport">
<div class="example-box" *cdkVirtualFor="let item of todo" cdkDrag>{{item}}</div>
</cdk-virtual-scroll-viewport>
</div>
</div>
<div class="example-container">
<h2>Done (regular list)</h2>
<div
cdkDropList
[cdkDropListData]="done"
class="example-list"
(cdkDropListDropped)="drop($event)">
<div class="example-box" *ngFor="let item of done" cdkDrag>{{item}}</div>
</div>
</div>
Typescript
import { Component, OnInit } from '@angular/core';
import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop';
@Component({
selector: 'app-thing',
templateUrl: './thing.component.html',
styleUrls: ['./thing.component.css']
})
export class ThingComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
todo = [
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'11',
'12',
'13',
'14',
'15',
'16'
];
done = [
'Get up',
'Brush teeth',
'Take a shower',
'Check e-mail',
'Walk dog'
];
drop(event: CdkDragDrop<string[]>) {
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);
}
//spread operator forces reload
this.todo = [...this.todo]
this.done = [...this.done]
console.log(this.done)
console.log(event)
}
}
CSS
.example-container {
width: 400px;
max-width: 100%;
margin: 0 25px 25px 0;
display: inline-block;
vertical-align: top;
}
.example-list {
border: solid 1px #ccc;
min-height: 60px;
background: white;
border-radius: 4px;
overflow: hidden;
display: block;
}
.example-box {
height: 61px;
/* padding: 20px 10px; */
border-bottom: solid 1px rgb(192, 11, 11);
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
0 8px 10px 1px rgba(0, 0, 0, 0.14),
0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-viewport {
height: 244px;
width: 400px;
}
.example-item {
height: 50px;
}
.whole_thing {
width: 3000px;
}
.bigscroller {
height: 600px;
width: 100%;
}
The issue is that you are using cdk-virtual-scroll-viewport
which is used to render huge lists. To do so the lists are sliced and only partial rendered and thats why your indeces are messed up.
A fix I thought of (not sure if its the best) is to pass the index given by the cdkVirtualFor
as drag data and use that to handle the order.
<cdk-virtual-scroll-viewport itemSize="62" class="example-viewport">
<div class="example-box" *cdkVirtualFor="let item of todo; let i = index;" cdkDrag [cdkDragData]="i">{{item}}</div>
</cdk-virtual-scroll-viewport>
and in the component
transferArrayItem(event.previousContainer.data,
event.container.data,
event.item.data,
event.currentIndex);
<cdk-virtual-scroll-viewport
formArrayName="items"
cdkDropList
[itemSize]="options.itemSize"
[perfectScrollbar]="config"
[cdkDropListDisabled]="!options.dragDrop"
(cdkDropListDropped)="onDragDrop($event)"
class="ps"
>
<div
*cdkVirtualFor="let item of itemsFiltered$ | async; let currentIndex = index"
cdkDrag
cdkDragLockAxis="y"
[cdkDragData]="currentIndex"
fxLayout="row"
class="row"
[class.drag]="options.dragDrop"
>
...
onDragDrop(event: CdkDragDrop<AbstractControl[]>) {
// Fix drag & drop indices inside virtual scroll
const previousIndex = event.item.data;
const adjustment = event.previousIndex - previousIndex;
moveItemInArray(this.items.controls, previousIndex, event.currentIndex - adjustment);
this.reindex();
this.emitChanges();
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.