I'm working with Angular Material and facing a challenge with the drag-and-drop feature using cdkDropList and cdkDrag. My goal is to synchronize drag-and-drop animations across two separate containers. Specifically, when an item in the first list is dragged, I want a corresponding item in the second list to mirror the drag animation.
Here's the structure of my current implementation:
<div
cdkDropList
cdkDropListOrientation="horizontal"
class="parent"
(cdkDropListDropped)="drop($event)"
>
@for(data of data1; track data){
<span
class="child"
cdkDragLockAxis="x"
cdkDragPreviewContainer="parent"
cdkDrag
>
{{ data }}
</span>
}
</div>
<div class="parent">
@for(data of data2; track data){
<span class="child"> {{ data }} </span>
}
</div>
export class DummyComponent {
protected readonly data1 = [1, 2, 3, 4];
protected readonly data2 = ['A', 'B', 'C', 'D'];
drop($event: CdkDragDrop<unknown[]>): void {
moveItemInArray(this.data1, $event.previousIndex, $event.currentIndex);
moveItemInArray(this.data2, $event.previousIndex, $event.currentIndex);
}
}
I also have a working example on StackBlitz that you can refer to: stackblitz.
Any ideas how to approach this (elegant)?
Yes, cdk-drag works adding style transform3d
. So we can get two ViewChildrens, the "original" and the "copy". We can store also the "index" of the element dragging and an array of "transformStyles"
dragIndex=-1;
transformStyle:any[]=[]
@ViewChildren('origin') origin!:QueryList<ElementRef>
@ViewChildren('copy') copy!:QueryList<ElementRef>
The idea is that our "copys" use this "transfromStyles", so our .html becomes like
<div cdkDropList ...>
@for(data of data1; track data;let i=$index){
<!--see the template reference variable "origin" -->
<span #origin class="child"
cdkDragLockAxis="x"
cdkDragPreviewContainer="parent"
cdkDrag
(cdkDragReleased)="release()" //<--we use cdkDragRelease
//when mouse up
(cdkDragStarted)="dragIndex=i" //<--simply equal dragIndex
(cdkDragMoved)="move($event.distance,i)" //<--here "recalculate" the transformStyle
//pass $event.distance
>
{{ data }}
</span>
}
</div>
<div class="parent fool" >
@for(data of data2; track data;let i=$index){
//we add the class.copy when is dragging
//and the class cdk-shadow to the element is dragging
<span #copy class="child" [class.copy]="dragIndex!=-1"
[class.cdk-shadow]="i==dragIndex"
//here the "style.transform"
[style.transform]="transformStyle[i]">
{{ data }}
</span>
}
</div>
Well
move(distance:any,index:number)
{
//see that we "equal" the styles to the "copys"
//else the element is dragging, that use "distance"
this.transformStyle=this.origin.toArray()
.map((x,i)=>i!=index? x.nativeElement.style.transform|| null:
'translate3d('+distance.x+'px,0px,0px)')
}
release()
{
//we get the position of the "place-holder"
const placeHolder=(document.getElementsByClassName('cdk-drag-placeholder')[0] as any).style
//and
this.transformStyle[this.dragIndex]=placeHolder.transform
}
Just only in dropped equal the dragIndex to -1 and remove the transform
drop($event: CdkDragDrop<unknown[]>): void {
moveItemInArray(this.data1, $event.previousIndex, $event.currentIndex);
moveItemInArray(this.data2, $event.previousIndex, $event.currentIndex);
this.transformStyle = this.transformStyle.map((_) => null);
this.dragIndex = -1;
}
Well, this NOT work propertly. The reason is than when we make the release, first the element is dragged position to the place and the executted the dropped. We need enclosed the release function in a ngZone. So we inject in constructor
constructor(private ngZone: NgZone) {}
And our function release becomes like
release() {
this.ngZone.run(() => {
const placeHolder = (
document.getElementsByClassName('cdk-drag-placeholder')[0] as any
).style;
this.transformStyle[this.dragIndex] = placeHolder.transform;
});
}
The stackblitz