Angular CDK brings Drag & Drop in Beta 7

   Angular
Drag and Drop

In this article we’ll go through one of the most anticipated features that all Angular developers have been waiting for  – Drag & Drop.

We’ll go through some example use cases of the feature with the API that the Angular Component Developer Kit (CDK) exposes. To begin, add or update the @angular/cdk package in your project.

npm install @angular/cdk@7.0.0-beta.0
OR
yarn add @angular/cdk@7.0.0-beta.0

Then include the package in your app.module.ts

import { DragDropModule } from '@angular/cdk/drag-drop';
@NgModule({
 imports: [BrowserModule, FormsModule, DragDropModule],
 declarations: [AppComponent],
 bootstrap: [AppComponent]
})
export class AppModule { }

Basic Drag & Drop

To make an element draggable, all you have to do is to add the cdkDrag directive on the element. And boom!

cdkDrag example

cdkDrag example

If we just apply cdkDrag to an element and move it around, the element is free to be dropped anywhere on the screen. However, in real apps, you may want to have some specific drop-zones where the element(s) would be dropped.

You can createcdk-drop elements that will tell the CDK that the draggable item is only allowed to drop inside this drop-zone. See the below example.

<cdk-drop> example

example

You can see that the element isn’t droppable outside of the cdk-drop element. You may also notice that while the element is being dragged, the original element still stays in its original position. This looks a bit clumsy and not what we usually see when we drag drop elements in everyday apps. We’re going to use some of the the classes that the Angular CDK provides to make this a little better. Hold your horses.

First off, we will make sure that when we drag the element, the placeholder (at the original position of the element) gets hidden so we only see the drag-preview. To do that, we’ll give the following styles in our styles.css file:

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-placeholder style example

.cdk-drag-placeholder style example

You can see that now when we’re dragging the element, there’s no placeholder present at the original position.

There’s still room for improvement because:

  • The drag preview looks larger than the original element which is weird.
  • When we leave the dragged element, the dragged elements quickly hides in a snap and the original element shows. There’s no transition involved.

To solve issue 1, we’ll be adding the following css to our component:

.cdk-drag-preview {
   box-sizing: border-box;
   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);
}

The box-sizing: border-box; does the trick and keeps the size of the preview same as the original element. The box-shadow is just added sugar.

.cdk-drag-preview style example

.cdk-drag-preview style example

For issue 2, we’ll add a transition to the dragged element using the following css:

.cdk-drag-animating {
  transition: transform 450ms cubic-bezier(0, 0, 0.2, 1);
}

.cdk-drag-animating style example

.cdk-drag-animating style example

Items Reordering

One common use-case of Drag & Drop is sorting or reordering lists. Angular CDK provides a clean way of doing this. Just apply cdkDrag directive to the list items and enclose the list within the cdk-drop element and that’s it.

See the below example:

<cdk-drop class="items-container">
 <div class="item" cdkDrag *ngFor="let user of users">
   <img class="item-image" [src]="user.picture.thumbnail">
   <div class="item-content">
     <p class="item-content-primary">
      {{user.name.first}} {{user.name.last}}
     </p>
     <p>
       {{user.email}}
     </p>
   </div>
 </div>
</cdk-drop>

Notice we have a drop zone enclosing all the draggable elements. Each element has the cdkDrag attribute which makes it draggable and the elements automatically move their position for the dragged element to be dropped at a desired position. See the preview below:

items reorder preview

items reorder preview

One important thing to note while implementing Drag & Drop with complex elements like in the above preview (having display: flex and a bunch of nested elements), is that the dragged preview may look weird. That is because Angular CDK currently does not copy the styles of the dragged elements. Instead it clones the markup of the dragged element. So to make sure that your dragged preview looks normal, do style the drag preview (.cdk-drag-preview) as well. You can see how we have handled this issue in this file:

.items-container .item,
.cdk-drag-preview {
  display: flex;
  align-items: center;
  border: 1px solid #eee;
  padding: 8px 10px;
}
.items-container .item .item-image,
.cdk-drag-preview .item-image {
  margin-right: 16px;
}
.items-container .item .item-content p,
.cdk-drag-preview .item-content p {
  font-size: 12px;
}
.items-container .item .item-content .item-content-primary,
.cdk-drag-preview .item-content .item-content-primary {
  font-size: 14px;
}
.items-container .item .item-content .item-content-primary,
.items-container .item .item-content p,
.cdk-drag-preview .item-content .item-content-primary,
.cdk-drag-preview .item-content p {
  margin: 0;
}

Applying the same styles as the list item to the .cdk-drag-preview element as shown above, we made sure that the preview looks exactly like the item.

You may have noticed that after dropping the item, the list remains unchanged. That is because Angular CDK just drops the item and the list (data array) remains unchanged until you handle the drop event and use Angular CDK’s moveItemInArray method to update the list array.

So first, in the template:

<cdk-drop class="items-container" (dropped)="drop($event)">
</cdk-drop>

Then in the component file:

import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
...
drop($event: CdkDragDrop<{title: string, poster: string}[]>) {
 moveItemInArray(
   this.users,
   $event.previousIndex,
   $event.currentIndex
 );
}

The example above for sorting uses a vertical list.

The example above for sorting uses a vertical list. However, if you had a horizontal list, the cdk-drop element will still assume that the list is vertical by default. You can change this by setting the orientation property of the cdk-drop element to horizontal.

Locking the drag axis

You can also lock the axis of the drag movement to either horizontal or vertical. To do so, just add cdkDragLockAxis="y" or cdkDragLockAxis="x" to the cdkDrag element.

Locking drag axis example

Locking drag axis example

Dragging using a cdkDragHandle

You can specify a custom area within the draggable element as a drag-handle by adding the cdkDragHandle attribute to the handle element. The whole draggable element will now be dragged only using this drag-handle.

<cdk-drop class="drop-zone">
 <app-box cdkDrag>
   <div class="drag-handle" cdkDragHandle>
     +
   </div>
 </app-box>
</cdk-drop>

You can specify a custom area within the draggable element as a drag-handle

Transferring items between drop-zones

If you had two sets of data and wanted to be able to drag an item from one set/list to another, you can do that by connecting the drop-zones (cdk-drop elements) using connectedTo attribute. The value of this attribute can be either a template variable or the id of the cdk-drop element to connect to.
See an example in the below template:

<cdk-drop #zone1 class="items-container"
  (dropped)="drop($event)"
  [data]="usersList1"
  [connectedTo]="zone2">
  ...
</cdk-drop>
<cdk-drop #zone2 class="items-container"
  (dropped)="drop($event)"
  [data]="usersList2"
  [connectedTo]="zone1">
  ...
</cdk-drop>

Notice that the first cdk-drop is connected to zone2 which is a template variable of the second cdk-drop element and vice versa.

The drop method for this scenario will be a bit different in our component class as it handles both cases when the item is dropped within the same zone, and when the item is dropped in the other zone. See the implementation in this file:

drop($event: CdkDragDrop<{title: string, poster: string}[]>) {
 if ($event.previousContainer === $event.container) {
   moveItemInArray(
     $event.container.data,
     $event.previousIndex,
     $event.currentIndex
   );
 } else {
   transferArrayItem(
     $event.previousContainer.data,
     $event.container.data,
     $event.previousIndex,
     $event.currentIndex
   );
 }
}

connectedTo example for transferring items between drop zones

connectedTo example for transferring items between drop zones

That is not it

This feature is still a work in progress and I have only focused on some of the main sub-features and use cases of the Drag & Drop. However, there is a lot more in the docs. Feel free to give Drag & Drop a try and let me know what you build using this amazing new feature in the comments. All of the examples and their code can be seen here.


Like What You See?

Got any questions?


>