Angular CDK drag and drop issue inside CSS flexbox

Paul Selle picture Paul Selle · Jan 24, 2019 · Viewed 11k times · Source

I ran into an issue using drag and drop module from the Angular CDK. I use it inside a container div which has (among others) the following CSS properties :

display: flex;
flex-wrap: wrap;

The flex_wrap property is here so that if the contained draggable elements don't fit in the container, they wrap into a second line and so on.

As the dragging is horizontal (cdkDropListOrientation="horizontal"), this works fine when all elements fit in a single line, but as soon as they wrap to a second line, drag and drop becomes buggy. I made the following stackblitz to reproduce the error : https://stackblitz.com/edit/angular-fytgp6 .

If anyone know how to fix this issue or thinks about a workaround for this, it would be of great help !

Answer

Jeff Gilliland picture Jeff Gilliland · Jan 30, 2019

This is a known issue with CDK Drag and Drop: https://github.com/angular/material2/issues/13372

Essentially, you need to have a parent div that is defined as a "cdkDropListGroup", then you need to treat each draggable item as a "cdkDropList" in addition to having the "cdkDrag" property on it. This should make it so that each item is its own container, and the "cdkDropListGroup" directive connects them all together.

Then, you can have a *ngFor on the cdkDropList container to spawn one for each of your array items. Put a [cdkDropListData]="index" with the cdkDropList so you can transfer the currently dragging index to the cdkDrag. With the child cdkDrag element, you can get this index with [cdkDragData]="index". Then, have an event binding (cdkDragEntered)="entered($event)" on the cdkDrag child which will fire every time you try to drag the element to one of the new containers. Inside the entered function, use the moveItemInArray method from the CDK to transfer the items around.

entered(event: CdkDragEnter) {
  moveItemInArray(this.items, event.item.data, event.container.data);
}
<div style="display:flex;flex-wrap:wrap" cdkDropListGroup>
  <div cdkDropList [cdkDropListData]="i" *ngFor="let item of items; let i = index;"  [style.width]="item.width || '100%'">
    <div cdkDrag [cdkDragData]="i" (cdkDragEntered)="entered($event)">
      {{item}}
    </div>
  </div>
</div>

If this doesn't work for you, then you can try using mat-grid instead to control your layout.

<mat-grid-list cdkDropListGroup>
  <mat-grid-tile cdkDropList [cdkDropListData]="i" *ngFor="let item of items; let i = index;" [colspan]="item.cols" [rowspan]="item.rows">
    <div cdkDrag [cdkDragData]="i" (cdkDragEntered)="entered($event)"> 
      {{item}}
    </div> 
  </mat-grid-tile> 
</mat-grid-list>