
Drag & drop is one of the best enhancements you can implement to give your app a modern feel and provide a great user experience, and Angular’s Component Dev Kit (CDK) makes it really easy to do. In this article, we’ll build a new Angular app from scratch using the Angular CLI and add a drag & drop todo list component.
Prereqs: Make sure you have the Angular CLI installed. You can test by running the command ng --version
. If you need to install it, run the command npm install -g @angular/cli
. (If you have problems installing on Linux, maybe this article can help!)
Step 1: Create a new app
Create a new app using the Angular CLI. We’ll also install the Angular CDK, and make sure it all works:
$ ng new my-app --defaults
$ cd my-app
$ npm install @angular/cdk
$ ng serve --open
Step 2: Import DragDropModule
In order to make use of Angular CDK’s drag & drop module, we need to import it. Open app.module.ts
and import DragDropModule
by making the following two edits:
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 { }
Step 3: Add a new component
Now we’ll use the Angular CLI again to add a new component:
$ ng generate component todo-list
Next we need to modify the component to handle the drop event. We’ll also add some hard-coded test data to work with. Open todo-list.component.ts
and make the following changes:
import { Component, OnInit } from '@angular/core';
import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop';
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html',
styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent implements OnInit {
tasks = [
'Cleaning',
'Gardening',
'Shopping'
];
constructor() { }
ngOnInit(): void {
}
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.tasks, event.previousIndex, event.currentIndex);
}
}
We also need to modify the component HTML. Update the contents of todo-list.component.html
to be the following:
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
<div class="example-box" *ngFor="let task of tasks" cdkDrag>{{task}}</div>
</div>
Step 4: Use the new component
At this point, we’re actually done adding the basic drag & drop functionality, so let’s use our new component. Replace the entire contents of app.component.html
with this:
<app-todo-list></app-todo-list>
Now, head back out to your terminal, re-launch the app, and–voila!–you’ve got drag & drop!
$ ng serve --open

(Optional) Step 5: Make it pretty
Sure it’s functional, but it doesn’t look very good! Spruce it up by adding the following CSS to todo-list.component.css
:
.example-list {
width: 500px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: block;
background: white;
border-radius: 4px;
overflow: hidden;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
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;
}
.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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
Ah, that feels a little better!

how would you properly write a unit test for this?
Hi, John. Great question. Take a look here: https://github.com/angular/components/blob/master/src/cdk/drag-drop/drag-utils.spec.ts
thanks for the response! These tests appear to just assert on moving an item in an array, and do not simulate the user dragging items using mouse events (mousedown, mousemove, mouseup).
I saw some examples but I could never get a simulated drag/drop event in the browser. Actually, in the JS console I can type this and simulate my Angular onDrop breakpoint. However, this doesn’t seem to work in a unit test.
““
var item = document.querySelectorAll(‘.c-selection-list-item’).item(2);
item.dispatchEvent(new MouseEvent(‘mousedown’));
item.dispatchEvent(new MouseEvent(‘mousemove’, {clientY: 10}));
item.dispatchEvent(new MouseEvent(‘mouseup’));
““
I think that it’s not wiring up right even though I include the DragDropModule in my current feature module.
`import { DragDropModule } from ‘@angular/cdk/drag-drop’;
But the component’s DOM don’t seem to respond to my mouse events.
Let me know if you have any ideas and thanks much for your help!
Why not just invoke the event that should fire? The drop event, I mean.
I understand it might be desirable or “more complete” to use the actual mouse events, but that’s the thing with unit tests–you always have to draw the line between what you’re testing and what you trust and can fake somewhere. In this case, isn’t it fair to trust that the Angular components will do their job and correctly handle those mouse/drag/drop events, and you can focus on handling their events?
You have a valid point about assuming that the CDK will be doing its job, but I always try to assert on the full the user interactiom within the component. That way if one of the CDK directives is accidentally removed from the template, then a test will fail.
However, after seeing how I’m unable to wire up the testbed correctly, I will probably will just end up using your approach and triggering the drop event directly, while asserting on the directives bring present.
Thanks again for the great article and your quick feedback!