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 thisAngular 9|8|7 Drag and Drop Across Multi Lists in Angular Material

 

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

 

# Create an Angular Project

Execute below 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

Move to the project directory

$ cd angular-material-drag-and-drop-lists

If you have Visual Studio Code( VS Code ) installed, run following to open in the project in VS Code

$ code .

Open application server on the browser.

$ ng serve --open

# Install Angular CDK Package

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.

$ npm install @angular/cdk

 

# Update App Module with 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>

Above is the list of tasks for Still Doing. 

cdkDropList directive as a droppable list

#pendingList is list identifier

cdkDropListData takes list object

cdkDropListConnectedTo 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.

cdkDropListDropped 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>

To make an item draggable, the 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.

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

 

# 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);
    }
  }
}

Above we have defined the array for items in 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">

 

Also, update the app.component.scss file, add the following style to make it look pretty

.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);
}

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.

 

# Bonus Tip

# Dynamic Inter Draggable & Droppable Lists

Sometimes we may need lists dynamically generated, in that case, we can use the id 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>

In this case, we have a dynamic JSON object 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.

Subscribe
Notify of
guest
9 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Nist

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.

bhumin

nee help to develop dynamic drag drop using two page ( mat-drawer-container ) to Tab Page
http://g.recordit.co/Ckw71c0A3n.gif

Alp Ergüney

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