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.  ## 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.
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.
It worked for me.
git clone https://github.com/shekhargulati/todomvc-angular-4.git
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.
If those who were looking for the repo:
https://github.com/shekhargulati/todomvc-angular-4
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?