1

I have designed a grid using angular. I am going to drag some images from the images list and drop them into grid tiles. Below I have attached the design

enter image description here

Here is my typescript code

officeItems = [Image's URL];

drop(event: CdkDragDrop<string[]>) {
if (event.previousContainer === event.container) {
  moveItemInArray(
    event.container.data,
    event.previousIndex,
    event.currentIndex
  );
} else {
  copyArrayItem(
    event.previousContainer.data,
    event.container.data,
    event.previousIndex,
    event.currentIndex
  );
}
}

HTML Code

<div class="row">
<div class="col-10" cdkDropList #cellList="cdkDropList" [cdkDropListData]="cells" [cdkDropListConnectedTo]="[itemList]">
  <mat-grid-list cols="10" >
    <mat-grid-tile *ngFor="let cell of cells" [style.background]="cell.color">
      <img *ngIf="cell.url" src="{{cell.url}}"/>
    </mat-grid-tile>
</mat-grid-list>
</div>

<div class="col-2" cdkDropList #itemList="cdkDropList" [cdkDropListData]="officeItems" [cdkDropListConnectedTo]="[cellList]" (cdkDropListDropped)="drop($event)">
  <pre *ngFor="let item of officeItems" cdkDrag><img src= "{{item}}" title="Sofa" style="margin-top: 10px;"/></pre>
</div>
</div>

Two data arrays in ts file

cells= [
{ id: 1, minDistanceToA: 0.0 },
{ id: 2, minDistanceToA: 1.0 },
{ id: 3, minDistanceToA: 2.0 },
{ id: 4, minDistanceToA: 3.0 },
{ id: 5, minDistanceToA: 4.0 }];

officeItems: any = ['../assets/images/sofa1.png', '../assets/images/chair.png'}];

I can drag images but can't drop them. When I try to drop, it replaces again to the previous div

3
  • What exactly is not working? Commented Jan 5, 2022 at 9:35
  • I can drag images but can't drop them. when drop it replaces again Commented Jan 5, 2022 at 9:37
  • 3
    Provide reproducible example Commented Jan 5, 2022 at 9:38

2 Answers 2

3

I know I'm late to the party, but...

To manage cdk-drag and drop in a "grid" it's very complex, because material angular cdkDropList is think about uni-dimension

The idea is always the same, instead using an unique cdkDropList, we can create so many cdkDropList as element we has -in out case, the grid. All enclosed in a div with cdkDropListGroup-

<div class="content" cdkDropListGroup>
  <div #board class="board">
    <mat-grid-list cols="5">
      <mat-grid-tile *ngFor="let cell of cells; let i = index">
        <div
          class="cell"
          cdkDropList
          [cdkDropListData]="cell"
          (cdkDropListDropped)="drop($event)"
          [style.background]="i == indexOver ? 'red' : 'yellowgreen'"
          (mouseover)="this.indexOver = i"
          (mouseout)="indexOver = -1"
        >
          <div cdkDrag>
            <img
              *ngIf="cell.src"
              [src]="cell.src"
              width="100%"
            />
            <span *ngIf="!cell.src"></span>
            <div *cdkDragPlaceholder></div>
          </div>
        </div>
      </mat-grid-tile>
    </mat-grid-list>
  </div>
  <div
    class="side"
    cdkDropList
    sortingDisabled="true"
    [cdkDropListData]="icons"
    (cdkDropListDropped)="drop($event)"
  >
    <div *ngFor="let icon of icons">
      <div cdkDrag (mousedown)="calculeMargin($event)">
        <img [src]="icon" />
        <img
          *cdkDragPreview
          [src]="icon"
          [ngStyle]="previewStyle ? previewStyle : null"
        />
        <div *cdkDragPlaceholder></div>
      </div>
    </div>
  </div>
</div>

Our mat-grid-tile will be the "cdkDrag", this show a img or a span acording the value of cell.src. See that we create a "empty" *cdkDragPlaceholder

In the other side, we has a typical list. We transform the cdkDragPreview to make that when we drag the element was in the same size of our "tiles" (it's the aim of the (mousedown)="calculeMargin($event")

I use the same function "drop" to control all drops. To check if we are dragging from the "board" or from the "side" I use the own "data". If has a property "src" is a item in the board, else is an item of the side. See that we not use "transfer" nor others "strange functions", else simply push or change the element of the array

  indexOver:number=-1;
  previewStyle: any = null;
  @ViewChild(MatGridTile, { static: false, read: ElementRef }) model;
  @ViewChild('board', { static: false,read:ElementRef })board:ElementRef;

  icons = [
    'https://picsum.photos/100/100?random=1',
    'https://picsum.photos/100/100?random=2',
    'https://picsum.photos/100/100?random=3',
  ];

  cells = Array(25).fill(' ').map((_,index)=>({src:null,id:index}));
  constructor() {}

  ngOnInit() {
    setTimeout(() => {
      const rect = this.model.nativeElement.getBoundingClientRect();
      this.previewStyle = {
        width: rect.width + 'px',
        height: rect.height + 'px',
      };
    });
  }
  calculeMargin(event: MouseEvent) {
    const rect = this.model.nativeElement.getBoundingClientRect();
    this.previewStyle = {
      width: rect.width + 'px',
        height: rect.height + 'px',
      'margin-top': -event.offsetY + 'px',
      'margin-left': -event.offsetX + 'px',
    };
  }
  drop(event: CdkDragDrop<any>) {
    if (event.previousContainer != event.container) {
      if (event.container.data.src!==undefined)
      {
        if (event.previousContainer.data.src!=undefined)
        {
          event.container.data.src=event.previousContainer.data.src
          event.previousContainer.data.src=null;
        }
        else
        {
          event.container.data.src=event.previousContainer.data[event.previousIndex]
        }
      }
      else
      {
        if (event.container.data.src===undefined && event.previousContainer.data.src!==undefined) 
        event.previousContainer.data.src=null;
      }
    }
  }

The stackblitz

Update really we can improve the code if we use two mat-grid-list. One for "board" and another one for "side"

<div class="content" cdkDropListGroup>
  <div #board class="board">
    <mat-grid-list cols="5">
        ...
    </mat-grid-list>
  </div>
  <div class="side">
    <mat-grid-list cols="1">
      <mat-grid-tile *ngFor="let cell of icons; let i = index">
        <div
          class="cell"
          cdkDropList
          [cdkDropListData]="cell"
          (cdkDropListDropped)="drop($event)"
        >
          <div cdkDrag>
            <img [src]="cell" width="100%" />
            <div *cdkDragPlaceholder></div>
          </div>
        </div>
      </mat-grid-tile>
    </mat-grid-list>
  </div>
</div>

See that the idea is similar, instead using an unique cdkDropList, for the side we can use the so many cdkDropList as icos we has. This allow us that, when "drag", it's not reorder the list of icons. futhermore, we can use a .css -see that it's thinking about a grid of 5x5- to make in the same size to the "icons" that the tiles of our board. This allow us not use the cdkPreview because all the "icons" has the same size.

.content
{
  display:flex;
  justify-content: space-between;
  position:relative;

}
.board{
  flex-basis:80%;
  position:relative;
}
.board::affter
{
  content:' '
}
.side{
  flex-basis:16%;
}

.cell{
  width:100%;
  height:100%;
}

The only change in the code is, when we make the drop use and remove the function "CalculeMargin"

    if (event.previousContainer.data.src!=undefined)
    {
        ....
    }
    else
    {
       //see that the data is the "src" of the icon
      event.container.data.src=event.previousContainer.data
    }

the new stackblitz

Sign up to request clarification or add additional context in comments.

1 Comment

Hi, @Eliseo Is it possible to have both horizontally and vertically cdkDrag and drop? I have created one demo here. I want to move items from one section to other. Items either be single or multiple. At present horizontally list is having some issue. Check this demo stackblitz.com/edit/… Thanks!
1

I have tried using your code and worked on it the first half of my day. I have come to the conclusion that combining mat-grid and cdkDropList is a bad idea. What I can tell you though is that using copyArrayItem is not getting you where you want to be.

Use this instead:

onDrop(event: CdkDragDrop<cell[]>) {
      if (!(event.previousContainer === event.container)){
         event.container.data[event.currentIndex] = 
         event.previousContainer.data[event.previousIndex];
      }
}

What this will do:

Container: Array with the cdkDragDrop items

Index: Used to pick out a single item

  1. Get the item you are dragging another item into (current container array at current index)
  2. Replace it with the item you are currently dragging (previous container array at previous index)

3 Comments

I have created cell array named "cells" in ts file and It says "Cannot find name 'cells' " when I use this code in ts file
I can't find those cells in the .ts that you shared with us. Would you mind showing us the whole ts?
added the cell array code sample in ts file

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.