Search code examples
angulartypescriptmergedrag-and-dropangular-material-7

Angular material drag and drop - merge elements


I'm building and app where there are multiple items rendered in set of columns. (For sake of demonstration let's say 4 columns)

I'm trying to achieve functionality of dragging and dropping items onto each other which will result in merge of those two items.

typescricpt data structure

Details{
     id:number;
     columns:Column[];
}

Column{
     id:number;
     item:Item[];
}
Item{
     id:number;
     text:string;
}

So I have details component with :

<div fxLayout="row wrap" fxLayoutAlign="center" fxLayoutGap="5px" cdkDropListGroup>
  <column *ngFor="let column of details.columns" [column]="column" fxFlex>
  </column>
</div>

And column component:

<div>
    Column Header
</div>
<div cdkDropList 
     (cdkDropListDropped)="drop($event)" 
     [cdkDropListData]="column.items"
     *ngFor="let item of column.items">
  <item cdkDrag [item]="item" [cdkDragData]="item">
  </item>
</div>

For now I just print it

drop(event: CdkDragDrop<Item[]>) {
   console.log(event);
}

Whenever I now print event after drop , I do have details of current container and moved onto , but I would require information about where exactly ( on which item.id ) I dropped that element, and not make item evade as default cdkDragDrop behaves. Afterwards I would have events for merging stuff on backend etc.

And hints would be appreciated

Link to example in stackblitz


Solution

  • I was able to find solution.

    Collegue of mine proposed to pass item on which event occurred in drop(event)

    So final solution would be :

    <div cdkDropList 
         (cdkDropListDropped)="drop($event,item)" 
         [cdkDropListData]="column.items"
         *ngFor="let item of column.items">
      <item cdkDrag [item]="item" [cdkDragData]="item">
      </item>
    </div>
    

    and handling ( using lodash )

    drop(event: CdkDragDrop<Item[]>, dropedOn: Item) {
        const dragged = event.item.data;
        if (dragged.id != dropedOn.id) {
            dropedOn.text = dropedOn.text + "\n" + dragged.text;
            if (event.previousContainer === event.container) {
                _.remove(event.container.data,(item)=>item.id== dragged.id);
            } else {
               _.remove(event.previousContainer.data,(item)=>item.id== dragged.id);
            }
         }
      }
    

    Updated stackblitz.

    However the whole solution was still bit too clunky , and it was hard to clearly show which element was to be merged into and eventually moved to other sollution ng-drag-drop.

    There every item is draggable and droppable and effect is bit nicer ( although requires extra searching of item to delete it from column)

    <div *ngFor="let item of column.items;" style="margin-top: 2px">
        <div draggable [dragData]="item" droppable (onDrop)="onItemDrop($event,item)">
                <item [item]="item"></item>
        </div>
    </div>
    

    And updated stackblitz with other solution