Search code examples
angularangular-materialangular-cdkangular-flex-layoutangular-cdk-drag-drop

How can I use Angular Material Drag n Drop with Flex Layout?


I am trying to make a grid that can have drag n drop capabilities and be responsive. First I tried using the grid list but I couldn't find how to make it responsive to different screen sizes.

I gave up on grid list and I decided to use the angular flex layout library to create my own grid that would be inherently responsive. Then I tried combining that with Angular Material Drag n Drop but it's not working as it should.

Specifically, I can drag around grid elements but the behavior is unstable at best. Sometimes I can reorder elements, sometimes not. Sometimes I move an element left and it goes right, sometimes the opposite. You get the picture. It's unpredictable. Another issue is that if you drag the element around, random other elements of the grid are appearing and disappearing.

I tried reading the documentation of drag and drop and I'm starting to get the feeling that it's just not supposed to work the way I want it to. Does anyone know of an implementation that might work for me?

Here is my code:

my-component.html

<div fxFlex fxLayout="column" fxLayoutGap="1%">
  <div fxLayout="row wrap" fxLayoutGap="16px grid"
       cdkDropList
       [cdkDropListData]="numbers"
       (cdkDropListDropped)="drop($event)">

    <div *ngFor="let n of numbers"
         fxFlex="25%"
         fxFlex.md="33%"
         fxFlex.sm="50%"
         fxFlex.xs="100%"
         cdkDrag>
      <div fxLayout="row" style="width: 200px; height: 200px; background-color: red;">
        Number: {{n}}
      </div>
    </div>

</div>

my-component.ts

import { Component, OnInit, ViewChild } from '@angular/core';
import { CdkDragDrop, CdkDropList, CdkDropListGroup, moveItemInArray } from "@angular/cdk/drag-drop";

@Component({
  selector: 'app-menu',
  templateUrl: './my-component.html',
  styleUrls: ['./my-component.scss']
})
export class MyComponent implements OnInit {
  @ViewChild(CdkDropListGroup) listGroup!: CdkDropListGroup<CdkDropList>;
  @ViewChild(CdkDropList) placeholder!: CdkDropList;

  numbers = [1, 2, 3, 4, 5, 6, 7, 8];

  constructor() {
  }

  ngOnInit(): void {
  }

  drop(event: CdkDragDrop<number[]>) {
    console.log("drop() prev index: " + event.previousIndex + ", cur index: " + event.currentIndex);
    moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
  }
}

Solution

  • I tried so hard to combine @angular/flex-layout and @angular/cdk/drag-drop but there were too many issues. So I did it with css flex and it is responsive.

    angular-drag-drop-flex-wrap

    import { Component } from "@angular/core";
    import { CdkDragDrop } from "@angular/cdk/drag-drop";
    
    @Component({
      selector: "my-app",
      templateUrl: "./app.component.html",
      styleUrls: ["./app.component.css"]
    })
    export class AppComponent {
      items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
      drop(event: CdkDragDrop<any>) {
        this.items[event.previousContainer.data.index] = event.container.data.item;
        this.items[event.container.data.index] = event.previousContainer.data.item;
      }
    }
    .categories {
      display: flex;
      justify-content: space-between;
      flex-wrap: wrap; /* NEW */
      width: 100%;
    }
    
    .inner {
      width: 100%;
      height: 100%;
      border: 1px solid blue;
      text-align: center;
      line-height: 5rem;
      background-color: #efefef;
      cursor: move;
    }
    
    .categories-item {
      flex: 1 0 5rem; /* NEW */
      margin: 5px; /* NEW */
      background-color: transparent;
      height: 5rem;
      text-align: center;
      line-height: 5rem;
      position: relative;
    }
    .placeholder {
      flex: 1 0 5rem; /* NEW */
      margin: 5px; /* NEW */
      background-color: white;
      height: 5rem;
      text-align: center;
      line-height: 5rem;
      border: 1px;
    }
    .cdk-drag-animating {
      transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
    }
    <div #contenedor class="categories" cdkDropListGroup>
        <ng-container *ngFor="let item of items;let i=index">
            <div class="categories-item" cdkDropList  cdkDropListOrientation="horizontal"
                [cdkDropListData]="{item:item,index:i}" (cdkDropListDropped)="drop($event)">
                <div class="inner" cdkDrag>
                    <div class="example-custom-placeholder" *cdkDragPlaceholder></div>
                    {{item}}
                </div>
            </div>
        </ng-container>
    </div>
    {{items|json}}