In this article we’ll go through one of the most anticipated features that all Angular developers have been waiting for – Drag & Drop.
NEW RESEARCH: LEARN HOW DECISION-MAKERS ARE PRIORITIZING DIGITAL INITIATIVES IN 2024.
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
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.
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
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
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
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:
{{user.name.first}} {{user.name.last}}
{{user.email}}
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
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:
Then in the component file:
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; ... drop($event: CdkDragDrop) { moveItemInArray( this.users, $event.previousIndex, $event.currentIndex ); }
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
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.
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:
... ...
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) { 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
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.
Ahsan Ayaz
Related Posts
-
How to Add Keyboard Navigation to Angular Lists
Originally published on NG-Conf Programming is fun, especially when you love the technology you’re working…
-
Ext JS to React: Drag and Drop
This is part of the Ext JS to React blog series. You can review the…