angulardrag-and-dropangular-cdkangular-cdk-drag-drop

While dragging, show if drop is allowed in angular CDK drag and Drop


Using angular CDK Drag and Drop (v17x) you can control if a drop is allowed using cdkDropListEnterPredicate. Works fine. But how do I make that visible on screen? Change the cursor or change a color, anything to inform the user if dropping there will be allowed. Is there any style set or any property?


Solution

  • Here is an approach using cursor: no-drop.

    First we store the currently dragging element number in a property, when the drag starts, for this we use the event (cdkDragStarted)="selectedDrag(number)".

    <div
      class="example-box"
      [cdkDragData]="number"
      cdkDrag
      (cdkDragStarted)="selectedDrag(number)"
    >
      {{number}}
    </div>
    

    After this, we simply need to apply the class to the drop elements, because the cursor that get's displayed is not the dragging elements cursor, but the elements below it. So we add the class using ngClass to the dropping elements as well as the container of the elements (to handle no elements in container scenario).

    <div class="example-container">
      <h2>Even numbers</h2>
    
      <div
        id="even"
        cdkDropList
        [cdkDropListData]="even"
        cdkDropListConnectedTo="all"
        class="example-list"
        [ngClass]="{'is-invalid': num && num % 2 !== 0 }"
        [cdkDropListEnterPredicate]="evenPredicate"
      >
        @for (number of even; track number) {
        <div
          class="example-box"
          cdkDrag
          [cdkDragData]="number"
          [ngClass]="{'is-invalid': num && num % 2 !== 0 }"
        >
          {{number}}
        </div>
        }
      </div>
    </div>
    

    Finally we add the CSS to the global styles to show the no--drop cursor.

    .cdk-drag.example-box.is-invalid {
      cursor: no-drop !important;
    }
    
    .is-invalid {
      cursor: no-drop !important;
    }
    

    Full Code:

    HTML:

    <div class="example-container">
      <h2>Available numbers</h2>
    
      <div
        id="all"
        cdkDropList
        [cdkDropListData]="all"
        cdkDropListConnectedTo="even"
        class="example-list"
        (cdkDropListDropped)="drop($event)"
        [cdkDropListEnterPredicate]="noReturnPredicate"
      >
        @for (number of all; track number) {
        <div
          class="example-box"
          [cdkDragData]="number"
          cdkDrag
          (cdkDragStarted)="selectedDrag(number)"
        >
          {{number}}
        </div>
        }
      </div>
    </div>
    {{showCursor | json}}
    <div class="example-container">
      <h2>Even numbers</h2>
    
      <div
        id="even"
        cdkDropList
        [cdkDropListData]="even"
        cdkDropListConnectedTo="all"
        class="example-list"
        [ngClass]="{'is-invalid': num && num % 2 !== 0 }"
        [cdkDropListEnterPredicate]="evenPredicate"
      >
        @for (number of even; track number) {
        <div
          class="example-box"
          cdkDrag
          [cdkDragData]="number"
          [ngClass]="{'is-invalid': num && num % 2 !== 0 }"
        >
          {{number}}
        </div>
        }
      </div>
    </div>
    

    TS:

    import { Component } from '@angular/core';
    import {
      CdkDragDrop,
      moveItemInArray,
      transferArrayItem,
      CdkDrag,
      CdkDropList,
    } from '@angular/cdk/drag-drop';
    import { CommonModule } from '@angular/common';
    
    /**
     * @title Drag&Drop enter predicate
     */
    @Component({
      selector: 'cdk-drag-drop-enter-predicate-example',
      templateUrl: 'cdk-drag-drop-enter-predicate-example.html',
      styleUrl: 'cdk-drag-drop-enter-predicate-example.css',
      standalone: true,
      imports: [CdkDropList, CdkDrag, CommonModule],
    })
    export class CdkDragDropEnterPredicateExample {
      showCursor = false;
      all = [1, 2, 3, 4, 5, 6, 7, 8, 9];
      even = [10];
      num: number | null = null;
      selectedDrag(num: number) {
        this.num = num;
      }
    
      drop(event: CdkDragDrop<number[]>) {
        if (event.previousContainer === event.container) {
          moveItemInArray(
            event.container.data,
            event.previousIndex,
            event.currentIndex
          );
        } else {
          transferArrayItem(
            event.previousContainer.data,
            event.container.data,
            event.previousIndex,
            event.currentIndex
          );
        }
      }
    
      /** Predicate function that only allows even numbers to be dropped into a list. */
      evenPredicate(item: CdkDrag<number>) {
        return item.data % 2 === 0;
      }
    
      /** Predicate function that doesn't allow items to be dropped into a list. */
      noReturnPredicate() {
        return true;
      }
    
      enter() {
        console.log('enter');
      }
    
      leave() {
        console.log('leave');
      }
    }
    

    Stackblitz Demo