Building an Angular 4 Drag And Drop Application in 15 Minutes


Last week, I had to build an application that required drag and drop functionality. This made me learn about couple of third party Angular 4 drag-and-drop APIs. The popular options in Angular 2+ world are ng2-dragula and ng2-dnd. I found ng2-dnd more extensible and feature rich so I used it in my application. In today’s blog, we will learn how to add drag-and-drop functionality to a todo application. We will use Addy Osmani Angular 4 todomvc application as the starting point. This post does not cover basics of how to build Angular 4 applications. There are many good references on the web that can teach you building Angular 4 applications from scratch.

Pro Programming Tip : Migrate your software development/testing environment into the hassle free cloud with high performance citrix xendesktop at an affordable xendesktop pricing from CloudDesktopOnline and remotely access your preferred programming tools such as emulators and IDE`s on your preferred device(PC/mac/Linux/android/iOS). Learn more about MS Azure and managed azure services by visiting Apps4Rent.

Github repository

The code for today’s post in on my Github repository shekhargulati/todomvc-angular-4.

Step 1: Clone the addyosmani/todomvc-angular-4 repository

We will start by cloning addyosmani/todomvc-angular-4 repository on our local machine. To do that you can run the git clone command shown below.

$ git clone git@github.com:addyosmani/todomvc-angular-4.git

Now, change directory to todomvc-angular-4. Install the dependencies and then start the application.

$ cd todomvc-angular-4
$ npm install && npm start

The application will start on port 4200 and will be accessible at http://localhost:4200.

Step 2: Install ng2-dnd

Next, we will install the main dependency of our application ng2-dnd. This library will add support for drag and drop functionality.

$ npm install ng2-dnd --save

Step 3: Import the DndModule

Next, we will import DndModule and declare it in the app.module.ts as shown below. In Angular every application has one application level module that bootstraps the application. The convention is to use AppModule as the name of the module class and app.module.ts as name of the file.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { DndModule } from 'ng2-dnd';
import { AppComponent } from './app.component';

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
DndModule.forRoot()
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

As shown in the code snippet above, we added declaration of DndModule in the imports section. imports defines all the dependencies of a module. As we want to use drag and drag capability provided by ng2-dnd so we declared that in the imports section. The forRoot is a convention for modules that expose a singleton service.

Step 4: Enable drag and drop capability

Now that we have imported DndModule we can add drag-and-drop functionality to our application. Update app.component.html to the one shown below.

<br /><section class="todoapp"><header class="header">
<h1>Todos</h1>
<input class="new-todo" title="" type="text" autofocus="" placeholder="What needs to be done?" />

</header><section class="main">
<ul class="todo-list">
    <li class="todo-item">
<div class="view">

{{todo.title}}

</div></li>
</ul>
</section><footer class="footer">
<span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span></footer></section>```

In the code snipped shown above, we did following:

1. We used `dnd-sortable-container` directive on `ul` to mark a container sortable. The reason we are using sortable to make sure items remain in sorted order. The `sortableData` input is use to specify the array data that will be managed by sortable container.
2. Next, we used `dnd-sortable` directive with `li` to tell that this item can be dragged and sorted. We used `sortableIndex` to specify position of the item. Items are sorted based on their index in the array.

The code changes made above will enable you to move items around the list by dragging and dropping them. Items will be sorted based on their index. Try it!

## Step 5: Add drag-and-drop handle

In the previous step, you could move the item by dragging the list item. These days it is common to have a handle to do it. If you have used Github repository milestone page, you will see that they make use of drag-and-drop handle there. The `ng2-dnd` library makes it dead simple to add the handle. All you have to do is use `dnd-sortable-handle` as shown below.

```html

<section class="todoapp"><header class="header">
<h1>Todos</h1>
<input class="new-todo" title="" type="text" autofocus="" placeholder="What needs to be done?" />

</header><section class="main">
<ul class="todo-list">
    <li class="todo-item">
<div class="view"><span class="handle">=</span>{{todo.title}}

</div></li>
</ul>
</section><footer class="footer">
<span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span></footer></section>```

Now, you will be able to drag only using the handle.

![](https://whyjava.files.wordpress.com/2017/09/todomvc-handle.png)

## Step 6: Taking action when item is moved

It is common to take actions when items are moved. ng2-dnd makes it easy to subscribe to such events.

```html

<section class="todoapp"><header class="header">
<h1>Todos</h1>
<input class="new-todo" title="" type="text" autofocus="" placeholder="What needs to be done?" />

</header><section class="main">
<ul class="todo-list">
    <li class="todo-item">
<div class="view"><span class="handle">=</span>{{todo.title}}

</div></li>
</ul>
</section><footer class="footer">
<span class="todo-count"><strong>{{todos.length}}</strong> {{todos.length == 1 ? 'item' : 'items'}} left</span></footer></section>```

In the code snippet shown above, all we did is to use Input to specify what data we want to drag and we specified even handler for `onDropSuccess` .

Add `onMove` handler method to `app.component.ts`.

```javascript
onMove(todo: Todo, position: number) {
this.todoDataService.moveTask(todo, position);
}

Component calls todo-data.service.ts. Currently, it only logs the message but it can do interesting stuff like calling the REST API.

moveTask(todo: Todo, position: number): void {
console.log(`Moved ${JSON.stringify(todo)} to the position ${position}`);
}

Step 7: Writing unit test

Let’s now write test case to test our functionality as well.

In the app.component.ts , we will write following test case to test the drag and drop functionality.

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms';
import { Todo } from './todo';
import { TodoDataService } from './todo-data.service';
import { By } from '@angular/platform-browser';
import { DndModule } from 'ng2-dnd';

describe('Todolist with drag-and-drop', () => {
let fixture;
let app;
let todoDataService;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
DndModule.forRoot()
],
declarations: [
AppComponent
],
providers: [TodoDataService]
});
});

beforeEach(async(() => {
fixture = TestBed.createComponent(AppComponent);
app = fixture.debugElement.componentInstance;
todoDataService = fixture.debugElement.injector.get(TodoDataService);
}));

it('should move todo at top to bottom', (done: any) => {
addTodo({
id: 1,
title: 'First Todo'
});
addTodo({
id: 2,
title: 'Second Todo'
});
addTodo({
id: 3,
title: 'Third Todo'
});
fixture.detectChanges();
const todoToDragEl = fixture.debugElement.queryAll(By.css('.todo-item'))[0].nativeElement;
const todoToDropEl = fixture.debugElement.queryAll(By.css('.todo-item'))[2].nativeElement;
const handleEl = fixture.debugElement.query(By.css('.handle')).nativeElement;
triggerEvent(handleEl, 'mousedown', 'MouseEvent');
triggerEvent(todoToDragEl, 'dragstart', 'MouseEvent');
triggerEvent(todoToDropEl, 'dragenter', 'MouseEvent');
triggerEvent(handleEl, 'mouseup', 'MouseEvent');
triggerEvent(todoToDragEl, 'drop', 'MouseEvent');
fixture.detectChanges();
expect(app.todos.map(t => t.id)).toEqual([2, 3, 1]);
done();
});

function addTodo(obj) {
app.newTodo = new Todo(obj);
app.addTodo();
}

function triggerEvent(elem: HTMLElement, eventName: string, eventType: string) {
const event: Event = document.createEvent(eventType);
event.initEvent(eventName, true, true);
elem.dispatchEvent(event);
}
});

You can run the test case by running npm test command.

Conclusion

In this post, we looked at how easy it is to add drag-and-drop capability to the application by using ng2-dnd library. Try it out.

5 thoughts on “Building an Angular 4 Drag And Drop Application in 15 Minutes”

  1. Is the repository public? I m getting this error

    Cloning into ‘todomvc-angular-4’…
    Permission denied (public key).
    fatal: Could not read from remote repository.

  2. Haven’t tried this out yet, but I’m looking forward to it. I did notice the ‘moveTask’ component function isn’t wrapped in a code block like the other code examples you have on the page. I figured it out, it was just a little confusing when I was reading through it. Anyway, thanks for the walkthrough example.

  3. Has anyone tried using ng2-dnd to drag an element from inside a Modal and drop it outside while closing the modal once the element drags past the modal container?

Leave a Reply

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 )

Facebook photo

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

Connecting to %s

%d bloggers like this: