Search code examples
angularangular-materialdrag-and-drop

How to get a list item on which another list item was dropped


Suppose I have two lists: a list of some products and a list of product categories. I would like to be able to move a product to a category by dropping it on a corresponding category list item. Here is the simple example:

<mat-list id="product-list" cdkDropList cdkDropListConnectedTo="category-list">
  <mat-list-item *ngFor="let product of products" cdkDrag>
      <span>product.name</span>
  </mat-list-item>
</mat-list>

<mat-list id="category-list" cdkDropList (cdkDropListDropped)="onDrop($event)">
  <mat-list-item *ngFor="let category of categories">
      <span>category.name</span>
  </mat-list-item>
</mat-list>

For now onDrop handler is called but I don't know how to determine a category list item on which a product list item was dropped. Since category list items are not draggable currentIndex is always zero in event.

Version of Angular/Angular Material is 12.1.3.


Solution

  • Finally I managed to solve this issue with elementFromPoint function. Here is the updated example:

    <mat-list id="product-list" cdkDropList>
      <mat-list-item *ngFor="let product of products" cdkDrag
                     (cdkDragEnded)="onDragEnded($event, product)">
        <span>product.name</span>
      </mat-list-item>
    </mat-list>
    
    <mat-list id="category-list">
      <mat-list-item *ngFor="let category of categories" class="drop-receiver"
                     (_drop)="onDrop($event, category)">
        <span>category.name</span>
      </mat-list-item>
    </mat-list>
    

    You can see that the lists are not connected anymore. Category list looks like an orginary one except its items have class drop-receiver and listener of custom _drop event.

    In product list I listen cdkDragEnded event. Here is the code of the event handler:

    onDragEnded(event: CdkDragEnd, product: Product) {
      const dropPointElement = document.elementFromPoint(event.dropPoint.x, event.dropPoint.y);
      if (dropPointElement) {
        const dropReceiver = dropPointElement.closest('.drop-receiver');
        if (dropReceiver) {
          const dropEvent = new CustomEvent('_drop', {detail: product});
          dropReceiver.dispatchEvent(dropEvent);
        }
      }
    }
    

    In example above I look for a DOM element using drop point coordinates. Then if element is found I look for a closest DOM element with class drop-receiver. Finally I send a custom event with name _drop to a drop receiver element. Now in handler of that event it it possible to implement some logic of assigning category to the selected product.