A Closer Look at the Drag and Drop Feature for Angular 7 CDK

In this post, we are going to take a close look at the drag and drop feature coming to Angular 7 Content Development Kit (CDK). We are getting closer to the release of Angular 7, so it is only fair to see what is in store for us. You can learn more about angular 7 here.

So, to checkout this feature, we will be creating a barebone to-do application. We will use the drag and drop, to move items from our to-do list to the done or cancelled lists. Our to-do application will have three lists, to-do items, done items and cancelled items. To cancel an item, you move it from either of the other two lists to the cancelled items.

Drag and Drop Demo

The same process is applied when moving an item from to-do item to done or cancelled list. You can also move items back and forth. You should also be able to re-arrange the list as you see fit by moving items up or down easily. For this app, we won’t persist items to keep thing simple, so everything will be lost when you reload.

For the UI we will use bootstrap, but you can use any UI library or framework you want to. So, without further ado:

Getting Started

For this project, we will be using the release candidate (RC) version of Angular 7. This is latest version of Angular 7 as of the time am writing this. This means, we also need the same version of Angular CLI to generate an Angular 7 application.

You can upgrade your global version of Angular CLI or install it in a directory and generate your new angular project from that directory. Since the local version of Angular CLI supersedes the global version, then any new project generated in that directory will use Angular 7. To learn more about how to install pre-release version of Angular 7, check the last section of the following article.

First, let’s create and navigate to a new directory to test out angular 7 using terminal or PowerShell. Then install a local version of pre-release version of Angular CLI using your favorite package manager

Yarn:

$ yarn add @angular/[email protected]

NPM

$ npm install @angular/[email protected]

Then, generate a new project using Angular CLI just you normally would:

$ ng new ng-material7-drag-and-drop-demo

Then change directory to that directory and add dependencies we require for this project:

Yarn

$ yarn add @angular/[email protected] bootstrap

NPM

$ npm install @angular/[email protected] bootstrap

Now, let’s create our to-do application:

Building to-do App

First, we need import the DragDropModule from Angular CDK  into our app module.

...
import { DragDropModule } from '@angular/cdk/drag-drop';
...

@NgModule({
  declarations: [AppComponent],
  imports: [
    ...
    DragDropModule,
    ...
  ],
  providers: [],
  bootstrap: [AppComponent]
})

export class AppModule {}

Next, let’s create an interface for our to-do list. Generate the interface using Angular CLI.

$ ng generate interface todo

Next, open the newly created typescript file and add the following content:

export interface Todo {
  title: string;
  dateAdded: string;
}

NB: This is a simple to-do application, so no need to make it too complicated, hence it has the title of the to-do and the date it was added only.

Adding Drag and Drop into Our Component

Component Class

First, we need three lists, which we shall add as properties of the class: todo, done and cancelled. We shall initialize the first to with some initial to-do and done items:

public todo: Todo[] = [
  { title: 'Get to work', dateAdded: new Date().toString() },
  { title: 'Pick up groceries', dateAdded: new Date().toString() },
  { title: 'Go home', dateAdded: new Date().toString() },
  { title: 'Fall asleep', dateAdded: new Date().toString() }
];

public done: Todo[] = [
  { title: 'Get up', dateAdded: new Date().toString() },
  { title: 'Brush teeth', dateAdded: new Date().toString() },
  { title: 'Take a shower', dateAdded: new Date().toString() },
  { title: 'Check e-mail', dateAdded: new Date().toString() },
  { title: 'Walk dog', dateAdded: new Date().toString() }
];

public cancelled: Todo[] = [];
Adding new To-Do Items

Next, we need a method to add new items to either the to-do or done lists:

addItem(list: string, todo: string) {
  if (list === 'todo') {
    this.todo.push({ title: todo, dateAdded: new Date().toString() });
  } else {
    this.done.push({ title: todo, dateAdded: new Date().toString() });
  }
}
Moving Items within the same list or to another Lists

And finally, a method that will be triggered when you drag and drop an item from one list to another.

drop(event: CdkDragDrop<Todo[]>) {
  // first check if it was moved within the same list or moved to a different list
  if (event.previousContainer === event.container) {
    // change the items index if it was moved within the same list
    moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
  } else {
    // remove item from the previous list and add it to the new array
    transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex);
  }
}

NB: The above method, either transfers an item between two arrays (lists) or moves a to-do item up or down in the array. First, you need to determine whether the user moved the content to another list, let’s say from to-do to done or they just re-arranged the items by moving an item up or down within the array. It uses the previousContainer and currentContainer properties to determine that. If the two are the same, then the user just re-arranged the list and not moving them. Then you use the transferArrayItem to move items to a different array, and moveItemInArray to move items within the same array.

Component Template

We are using Bootstrap for the UI but feel free to use any UI Framework you are comfortable with. So, first we need to add some input controls to add new items to our to-do list:

<div class="row">
  <div class="col-6">
    <div class="form-group">
      <label>New Task</label>
      <input #todoitem class="form-control">
    </div>
  </div>

  <div class="col-6">
    <div class="form-group">
      <label>List</label>
      <select #todolist class="form-control">
        <option selected value="todo">To Do</option>
        <option value="done">Done</option>
      </select>
    </div>
  </div>

  <div class="col-12 text-right">
    <button (click)="addItem(todolist.value, todoitem.value)" class="btn  btn-primary">
      Add Item
    </button>
  </div>
</div>

Next, let’s add drag and drop list to our template. For this, we will be using the cdk-drop to wrap around each of our lists (to-do, done and cancelled list). We will pass the following properties:

  1. Id (#todoList) – the id of the drag and drop list
  2. Data source ([data]) – the data source of the list i.e. to-do, cancelled and done arrays,
  3. Drag and Drop lists its connected to ([ConnectedTo]) – This sets limits to which lists the user can drag and drop items to.
  4. And finally, the event that is called when a user drops an item (dropped) .

So, our first list for to-do items will look like this. We are using *ngFor directive to display the actual list inside the drag and drop list.

<!-- to-do items list -->
<cdk-drop class="list-group list-group-flush" #todoList [data]="todo" [connectedTo]="[doneList, cancelledList]" (dropped)="drop($event)">
  <div class="list-group-item list-group-item-primary" *ngFor="let item of todo" cdkDrag>
    <p class="mb-1"> {{ item.title | titlecase }} </p>
    <small class="text-muted"> {{ item.dateAdded | date:'short' }}</small>
  </div>
</cdk-drop>

And the same goes for done and cancelled lists:

<!-- Done to-do items list -->
<cdk-drop #doneList [data]="done" [connectedTo]="[todoList, cancelledList]" class="list-group list-group-flush" (dropped)="drop($event)">
  <div class="list-group-item list-group-item-success" *ngFor="let item of done" cdkDrag>
    <p class="mb-1"> {{ item.title | titlecase }} </p>
    <small class="text-muted"> {{ item.dateAdded | date:'short' }} </small>
  </div>
</cdk-drop>
<!-- Cancelled to-do items list -->
<cdk-drop #cancelledList [data]="cancelled" [connectedTo]="[todoList, doneList]" class="list-group list-group-flush"  (dropped)="drop($event)">
  <div class="list-group-item list-group-item-warning" *ngFor="let item of cancelled" cdkDrag>
    <p class="mb-1"> {{ item.title | titlecase }} </p>
    <small class="text-muted"> {{ item.dateAdded | date:'short' }} </small>
  </div>
</cdk-drop>

And finally, you can now wrap each of those drag and drop lists with a bootstrap card component:

<div class="card bg-warning">
  <div class="card-header text-white">
     <h2>Cancelled</h2>
  </div>
  <!-- Lists Here -->
</div>

You can find the complete template here.

Source Code and Demo

You can find the demo for the above project here and the source code here.

4 Replies to “A Closer Look at the Drag and Drop Feature for Angular 7 CDK”

  1. Hi,
    Thanks for the write up.

    I couldn’t get the drag and drop to work on the cancelled section of the demo. Could it be a browser specific bug? I’m on firefox.

    1. I had not tested on firefox, and I just did some testing and it seems to be working differently on different browser, I will try and add some padding to the cancelled section see if it works. Also Microsoft Edge has the same issue but it works on Opera, so I think it depends on the rendering engine in question and how it handles that empty cancelled list. I will report over the weekend with my findings.

    1. It should, because other material components work with Server Side Rendering. I will run some tests to confirm that though.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.