E2E Angular Drag & Drop App in 5 Steps

Image by sachin_21 from Pixabay

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!

Author: Adam Prescott

I'm enthusiastic and passionate about creating intuitive, great-looking software. I strive to find the simplest solutions to complex problems, and I embrace agile principles and test-driven development.

7 thoughts on “E2E Angular Drag & Drop App in 5 Steps”

      1. 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!

      2. 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?

      3. 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!

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s