Angular 9|8|7 Drag and Drop Across Multi Lists in Angular Material

In this Angular 9 tutorial, we’ll discuss how to implement drag and drop functionality using Angular Material library components. In the drag and drop lists, items can be dropped across multiple lists or in the same list.

In the Angular Material version 7, two major updates were introduced, Virtual Scroll, and Drag and Drop. Both these features prove very useful and productive from a user perspective. In this post, we will create an application to demonstrate the Drag and Drop functionality using the CDK’s DragDropModule in Material 9.

The Drag and Drop feature enables a user to move anything from one place to another in a web application with the help of a mouse, this helps in creating an interactive, highly simple clean user interface. In our application we will have three user lists i.e Still Doing, Done and Nevermind. A user can drop an item from any list and can drop to any other list.

After completing our application will look like this

 

Let’s start …

Also, check post on Angular Material 7 Virtual Scroll Example

# Setup Angular CLI

We’ll create Angula project in the latest version. Make sure you have updated the Angular CLI tool by running below npm command in the terminal

$ npm install -g @angular/cli</pre>
 
<h3># Create an Angular Project</h3>
Execute below <code>ng command to create an Angular project in latest version 9.1.3. But this tutorial is compatible with previous version 7,6,5 and 4
$ ng new angular-material-drag-and-drop-lists
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? SCSS</pre>
Move to the project directory
<pre class="wp-block-prismatic-blocks"><code class="language-javascript">$ cd angular-material-drag-and-drop-lists</pre>
If you have Visual Studio Code( VS Code ) installed, run following to open in the project in VS Code
<pre class="wp-block-prismatic-blocks"><code class="language-javascript">$ code .</pre>
Open application server on the browser.
<pre class="wp-block-prismatic-blocks"><code class="language-javascript">$ ng serve --open</pre>
<h4></h4>
<h3># Install Angular CDK Package</h3>
The Drag and Drop components are available in the Component Dev Kit (CDK) package. Components are available in the CDK work as the core functionalities available in the Material UI library without any Material design. Here also we will use the Bootstrap CSS style for our Drag and Drop lists.

Run following npm command to instal the CDK package in the project to use its modules.
<pre class="wp-block-prismatic-blocks"><code class="language-javascript">$ npm install @angular/cdk</pre>
 
<h3># Update App Module with <code>DragDropModule
The CDK package provides number of functionalities. Here we need to import the DragDropModule to use its functionality.

Open the app.module.ts file, import the DragDropModule then update the modules array as shown below:
// 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';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    DragDropModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

# Create a Draggable List

A draggable wrapper with items to be sortable is created by adding the cdkDropList. The Items in the list is created dynamically by defining the data Object in the [cdkDropListData].

If we want to multiple lists with items to be draggable or droppable inter between then we also add the [cdkDropListConnectedTo] property as well.

          <div class="drag-container">
              <div class="section-heading">Still Doing</div>
          
              <div cdkDropList #pendingList="cdkDropList" [cdkDropListData]="todo" [cdkDropListConnectedTo]="[doneList,reviewList]"
                class="item-list" (cdkDropListDropped)="drop($event)">
                <div class="item-box" *ngFor="let item of todo" cdkDrag>{{item}}</div>
              </div>
          </div></pre>
Above is the list of tasks for <strong>Still Doing. </strong>

<code><strong>cdkDropList</strong> directive as a droppable list

<strong>#pendingList</strong> is list identifier

<strong>cdkDropListData</strong> takes list object

<strong>cdkDropListConnectedTo</strong> tells about, to which list it can communicate or drag and drop lists. We can add any number of lists, it takes an array of identifier list names.

<strong>cdkDropListDropped</strong> binds and event, fired when an item is dropped in this list.

 

# Multiple DragDrop Lists

As a part of our application following is the final template to be placed in the app.component.html file. There are thee lists in which items can drag and drop to any list in between. The three task lists are Still Doing, Done and Nevermind. These are connected to each other by defining the array of the list defines in the [cdkDropListConnectedTo] property.
<div class="container">
    <div class="row">
      <div class="col-md-4">
          <div class="drag-container">
              <div class="section-heading">Still Doing</div>
          
              <div cdkDropList #pendingList="cdkDropList" [cdkDropListData]="todo" [cdkDropListConnectedTo]="[doneList,reviewList]"
                class="item-list" (cdkDropListDropped)="drop($event)">
                <div class="item-box" *ngFor="let item of todo" cdkDrag>{{item}}</div>
              </div>
          </div>
      </div>
      <div class="col-md-4">
          <div class="drag-container">
              <div class="section-heading">Done</div>
          
              <div cdkDropList #doneList="cdkDropList" [cdkDropListData]="done" [cdkDropListConnectedTo]="[pendingList,reviewList]"
                class="item-list" (cdkDropListDropped)="drop($event)">
                <div class="item-box" *ngFor="let item of done" cdkDrag>{{item}}</div>
              </div>
            </div>
      </div>
      <div class="col-md-4">
          <div class="drag-container">
              <div class="section-heading">Nevermind</div>
          
              <div cdkDropList #reviewList="cdkDropList" [cdkDropListData]="review" [cdkDropListConnectedTo]="[doneList,pendingList]"
                class="item-list" (cdkDropListDropped)="drop($event)">
                <div class="item-box" *ngFor="let item of review" cdkDrag>{{item}}</div>
              </div>
            </div>
      </div>
    </div>
</div>
</pre>
To make an item draggable, the <code>cdkDrag directive is added. The drop event handler is defined in the (cdkDropListDropped)  which is triggered when the item is placed in a list. It returns the following information about the previous and new container index number.



 

# Update App Class with List Data and Drop Method

In app.components.ts we will define list objects and Droppable Event to arrange items in a list after a new item is added or removed from another list.
import { Component } from '@angular/core';
import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  
  
  todo = [
    'Get to work',
    'Pick up groceries',
    'Go home',
    'Fall asleep'
  ];

  done = [
    'Get up',
    'Brush teeth',
    'Take a shower',
    'Check e-mail',
    'Walk dog'
  ];

  review = [
    'Take bath',
    'Wash car',
  ];

  drop(event: CdkDragDrop<string[]>) {
    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);
    }
  }
}
</pre>
Above we have defined the array for items in <code>todo, done and review arrays. The drop() method is handling the sorting and drop work by using the moveItemInArray() and transferArrayItem() methods available in the '@angular/cdk/drag-drop'package.

 

# Styling the lists

To style our DragDrop lists we used Bootstrap CSS, just add css file in the index.html file's head section
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"  crossorigin="anonymous"></pre>
 

Also, update the <strong>app.component.scss</strong> file, add the following style to make it look pretty
<pre class="lang:sass decode:true">.section-heading {
  padding: 5px 10px;
  font-size: 15px;
  font-weight: bold;
}

.drag-container {
  width: 400px;
  max-width: 100%;
  margin: 0 25px 25px 0;
  display: inline-block;
  vertical-align: top;
  background-color: #E9ECEF;
  padding: 15px;
  border-radius: 5px;
}

.item-list {
  min-height: 60px;
  border-radius: 4px;
  display: block;
}

.item-box {
  padding: 8px 10px;
  border: solid 1px #ccc;
  margin-bottom: 5px;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
  border-radius: 20px;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
              0 8px 10px 1px rgba(0, 0, 0, 0.14),
              0 3px 14px 2px rgba(0, 0, 0, 0.12);
  border-radius: 20px;
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.item-list.cdk-drop-list-dragging .item-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}</pre>
That's all, now you will be able to see the working application having three lists, from which you can drag any item to drop back in same or another list.

 
<h3># Bonus Tip</h3>
<h3># Dynamic Inter Draggable & Droppable Lists</h3>
Sometimes we may need lists dynamically generated, in that case, we can use the <code><strong>id</strong> in place of # template variables as shown below.
    <div class="col-md-3" *ngFor="let week of weeks">
      <div class="drag-container">
        <div class="section-heading">Week {{week.id}}</div>

        <div cdkDropList id="{{week.id}}" [cdkDropListData]="week.weeklist"
          [cdkDropListConnectedTo]="connectedTo" class="item-list" (cdkDropListDropped)="drop($event)">
          <div class="item-box" *ngFor="let weekItem of week.weeklist" cdkDrag>Week {{week.id}} {{weekItem}}</div>
        </div>
      </div>
    </div></pre>
In this case, we have a dynamic JSON object <code>weeks of multiple lists item as shown below. The connectedTo array will have the list identifiers which are connected to each other.
//app.component.ts
import { Component } from '@angular/core';
import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop';

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

  weeks = [];
  connectedTo = [];
  
  constructor(){
    this.weeks = [
      {
        id:'week-1',
        weeklist:[
          "item 1",
          "item 2",
          "item 3",
          "item 4",
          "item 5"
        ]
      },{
        id:'week-2',
        weeklist:[
          "item 1",
          "item 2",
          "item 3",
          "item 4",
          "item 5"
        ]
      },{
        id:'week-3',
        weeklist:[
          "item 1",
          "item 2",
          "item 3",
          "item 4",
          "item 5"
        ]
      },{
        id:'week-4',
        weeklist:[
          "item 1",
          "item 2",
          "item 3",
          "item 4",
          "item 5"
        ]
      },
    ];
    for (let week of this.weeks) {
      this.connectedTo.push(week.id);
    };
  }

  drop(event: CdkDragDrop<string[]>) {
    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);
    }
  }
}

 

Check the working demo link.

Also, check post on Angular Material 7 Virtual Scroll Example

Get source code in the GitHub Repo here.

Conclusion: Here we discussed how to make lists with drag and drop functionality using the CDK library. We can easily customize the functionality and styling due to its flexible configurations available.

11 thoughts on “Angular 9|8|7 Drag and Drop Across Multi Lists in Angular Material”

  1. Hi, thanks a lot for this well explained tutorial. The first part works like a charm. However, the dynamic part does not seem to work. Could you confirm that this actually works? I tried creating a new blank project, adding a component for both examples and only the first one (non dynamic) works. I tried debugging but without any succes. Thanks a lot for your help.

  2. Hey mate,
    Great article! However, I need to create my lists in the run time as I have a week view component where you can navigate through weeks. Week view comprises day views, and I’m creating them with a loop rather than adding the same code 7 times i.e. once for each day.

    #dayList=”cdkDropList” [cdkDropListData]=”dayData” [cdkDropListConnectedTo]=”[dayList]”
    dayData array works fine but using #dayList variable as the only parameter in [cdkDropListConnectedTo] seems to be an issue.

    Any tips on how I can make it work would be amazing.

    Cheers

    1. Hi, please check updated example under “Dynamic Draggable & Droppable Lists” here we have used ID’s instead f # template variables. let me know if it helped 🙂

          1. Try removing [cdkDropListConnectedTo] as we have only one list where items are sorting in self

          2. Tried that already but no dice… Both indexes still return 0. Saved it to same stackblitz link if you’d like to check.

Leave a Comment

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