create angular CDK multi drag, drop and multi sorting

Why this post?

Angular CDK has beautiful drag, drop, and sort facilities, but we only able to move only one at a time. So, I give a solution to move with multiple selections of lists.

Angular CDK multi drag and drop with sorting video

Let see step by step from the package installation requirement.

npm i @angular/cdk

Step 1 – Next to import the needed module

import {DragDropModule} from ‘@angular/cdk/drag-drop’;

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import {DragDropModule} from '@angular/cdk/drag-drop';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,FormsModule,CommonModule, DragDropModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

I am using bootstrap jquery for Front End. If you don’t know how to import in angular. See this link.

Step 2 – Import some services like moveItemInArray, transferArrayItem form CDK drag-drop module.

import { CdkDragDrop, moveItemInArray, transferArrayItem } from ‘@angular/cdk/drag-drop’;

app.component.ts

import { Component } from '@angular/core';

import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';

declare var $: any;
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  public tasks = [{
    title: 'All Tasks',
    taskList: [{
      id: 1,
      selected: false,
      name: 'Task 1'
    }, {
      id: 2,
      selected: false,
      name: 'Task 2'
    }, {
      id: 3,
      selected: false,
      name: 'Task 3'
    }, {
      id: 4,
      selected: false,
      name: 'Task 4'
    }, {
      id: 5,
      selected: false,
      name: 'Task 5'
    }, {
      id: 6,
      selected: false,
      name: 'Task 6'
    }, {
      id: 7,
      selected: false,
      name: 'Task 7'
    }, {
      id: 8,
      selected: false,
      name: 'Task 8'
    },
    {
      id: 9,
      selected: false,
      name: 'Task 9'
    }, {
      id: 10,
      selected: false,
      name: 'Task 10'
    },
    ]
  }, {
    title: 'Monday',
    taskList: []
  }, {
    title: 'Tuesday',
    taskList: []
  }, {
    title: 'Wednesday',
    taskList: []
  }, {
    title: 'Thursday',
    taskList: []
  }, {
    title: 'Friday',
    taskList: []
  }
  ];
  public ctrlPress = false;
  public dragData = [];
  public stored = [];
  public sortIndex = [];

  ondragging(event, item, data) {
    this.dragData = data;
    const a = this.stored.findIndex(x => x.id === item.id);

    if (a === -1) {
      item.selected = true;
      let idx = data.indexOf(item);
      this.sortIndex.push(idx);
      this.sortIndex.sort();
      this.stored = [];
      this.sortIndex.forEach(i => {
        data[i].selected = true;
        this.stored.push(data[i]);
      });

    } else {

    }



    $('.selected').addClass('dragging');

    $('.cdk-drag-placeholder').css({ 'opacity': '0 !important' });


  }


  drop(event: CdkDragDrop<any[]>, dropdata) {
    $('.selected').removeClass('dragging');
    $('.selected').css({ 'opacity': 1 });
    $('.cdk-drag-placeholder').css({ 'opacity': 1 });
    if (this.stored.length === 1) {
      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);
      }
      this.stored = [];
      event.container.data.forEach((d) => {
        d.isMultidragging = false;
        d.selected = false;
      });
    } else {
      const storedCloner = JSON.parse(JSON.stringify(this.stored));
      if (event.previousContainer === event.container) {
        let b = event.currentIndex;
        const clonerdata = Object.assign([], event.previousContainer.data);

        const extra = Math.max.apply(Math, this.sortIndex.map((o) => { return o; }));
        if (extra < b) {
          b = b + 1;
        }
        clonerdata.forEach((elem, i) => {
          if (elem.selected) {
            const b = dropdata.findIndex(x => x.id === elem.id);
            dropdata[b].id = 0;

          }
          if (clonerdata.length === (i + 1)) {
            storedCloner.slice(0).reverse().forEach((item, idx) => {
              dropdata.splice(b, 0, item);
              if (storedCloner.length === (idx + 1)) {
                setTimeout(() => {
                  dropdata.forEach((el, index) => {
                    if (el.id === 0) {
                      dropdata.splice(index, 1);
                    }
                  }, 1000);
                });

              }
            });
          }
        });


        this.stored = [];
        event.container.data.forEach((d, id) => {
          if (d.id === 0) {
            event.container.data.splice(id, 1);
          }

          d.isMultidragging = false;
          d.selected = false;
        });


      } else {
        if (this.stored.length > 0) {
          event.previousContainer.data.slice(0).reverse().forEach((item, idx) => {
            if (item.selected) {
              event.previousContainer.data.splice(event.previousContainer.data.indexOf(item), 1);

              event.container.data.splice(event.currentIndex, 0, item)
              event.container.data.forEach((d) => {
                // d.isMultidragging=false;
                d.selected = false;
              })

            }
          });
          this.stored = [];

        } else {

          transferArrayItem(event.previousContainer.data,
            event.container.data,
            event.previousIndex,
            event.currentIndex);
          event.container.data.forEach((d) => {
            d.isMultidragging = false;
            d.selected = false;
          });
          this.stored = [];

        }
      }

    }

    this.sortIndex = [];


  }

  onKeyDown(e, item, data) {
    this.ctrlPress = e.ctrlKey;
    if (e.ctrlKey) {
      const idx = data.indexOf(item);
      this.sortIndex.push(idx);

      this.stored = [];
      const result = [];
      const map = new Map();
      for (const item of this.sortIndex) {
        if (!map.has(item)) {
          map.set(item, true);    // set any value to Map
          result.push(item);
        }
      }
      this.sortIndex = result;
      this.sortIndex.forEach(i => {

        data[i].selected = true;
        this.stored.push(data[i]);

      });
    }

  }

}

The above code has all functions like
1. ondragging() // drag started
2. drop() // on drop the dragged data
3. onKeyDown() // selection of list

We have a task list in the above example for Monday to Friday to assign the task.

ondragging() function doing when dragging the unselected element its check and added to the already item selected to drag. So when dropped if any selected item there will also be dropped into the container.

drop() function – It helps to drop the task in one container and remove the task from the previous container.

onKeyDown() function – It helps to select an item or deselect an item from the task list.

Step 3 – Add HTML for the front end view

app.component.html

<div class="container-fluid mt-2">
  <div class="container" cdkDropListGroup>
    <div class="row d-flex justify-content-start">

      <div class="col-md-4 mb-3" *ngFor="let task of tasks; let i = index">
        <div class="card">
          <div class="header">
            <h6 class="p-2 bg-secondary text-white text-uppercase">{{task.title}}</h6>

          </div>
          <div class="card-body p-0" style="height:300px;overflow:auto" cdkDropList [cdkDropListData]="task.taskList"
            (cdkDropListDropped)="drop($event, task.taskList)">
            <ul class="list-group">

              <li class="list-group-item border" *ngFor="let t of task.taskList" cdkDrag
                (cdkDragStarted)="ondragging($event, t, task.taskList)" (click)="onKeyDown($event, t, task.taskList)"
                [ngClass]="{'border-primary':t.selected,'selected':t.selected}">{{t.name}}
                <div *cdkDragPreview>
                  <ul class="list-group">
                    <li class="list-group-item border" *ngFor="let s of stored" style="min-width:200px;">{{s.name}}</li>
                  </ul>
                </div>
              </li>
            </ul>
          </div>

        </div>

      </div>

    </div>

  </div>
</div>

Important to know the details of
1. cdkDropListGroup
2. cdkDropList
3. [cdkDropListData]
4. (cdkDropListDropped)
5. cdkDrag
6. (cdkDragStarted)

cdkDropListGroup => If you add to the top of the drag-drop containers. It says any drop list can able to accept the dragging data. If you want to some container will accept only form particular then you can use [cdkDropListConnectedTo] . Its an array you can push the reference in to drop list connected.

cdkDropList => It says that the container will accept the dragging data.

[cdkDropListData] => You will add task list data into this.

(cdkDropListDropped) => It is an event triggering when dropping initiated.

cdkDrag => Which says that the elements are draggable.

(cdkDragStarted) => It is an event triggering when drag started.

Step 4 – Add some CSS for better visibility.

app.component.css

.card-body::-webkit-scrollbar {
  width: 7px;
}
 
.card-body::-webkit-scrollbar-track {
  box-shadow: inset 0 0 10px transparent;
}
 
.card-body::-webkit-scrollbar-thumb {
  background-color: #c1c1c1;;
 border-radius:3.5px;
}
.selected.dragging{
  opacity:0.2;
}
.selected.dragging.cdk-drag-placeholder{
  opacity:0 !important;
}

That’s all. I hope this code will help you with something. Thanks for reading.

NOTE: If any queries feel free to ask in comment.

Leave a Reply

Your email address will not be published. Required fields are marked *