The @angular/cdk/drag-drop
module helps us drag single item or sort it within a horizontal/vertical list, transfer items between lists, show animations, previews, placeholders, custom drag handles. In this tutorial, we’re gonna create many simple examples that show you how to work with Angular 7 Material CDK – Drag and Drop.
Related Post: Angular 7 Virtual Scroll example – Angular Material CDK
Setup Angular 7 Material CDK
– Run cmd:
npm install @angular/material @angular/cdk
– Import DragDropModule
into NgModule
:
... import { DragDropModule } from '@angular/cdk/drag-drop'; @NgModule({ declarations: [...], imports: [ ... DragDropModule ], ... }) export class AppModule { }
Angular 7 Drag and Drop item
Basic
Add the cdkDrag
directive to element to make them draggable:
<div class="drag-box" cdkDrag> drag me </div>
Lock Direction
Set cdkDragLockAxis
on cdkDrag
to restrict dragging:
<div class="drag-box" cdkDragLockAxis="y" cdkDrag> only up/down </div> <div class="drag-box" cdkDragLockAxis="x" cdkDrag> only left/right </div>
Event Handler
Add functions on cdkDrag
to handle events:
– cdkDragStarted
: emits when user stops dragging.
– cdkDragEnded
: emits when user starts dragging.
– cdkDragMoved
: emits when user is dragging the item (for every pixel).
<div cdkDrag class="drag-box" (cdkDragStarted)="dragStarted($event)" (cdkDragEnded)="dragEnded($event)" (cdkDragMoved)="dragMoved($event)" > drag me </div> <p>{{state}} {{position}}</p>
implement the functions:
import { CdkDragEnd, CdkDragStart, CdkDragMove } from '@angular/cdk/drag-drop'; ... export class DragComponent implements OnInit { state = ''; position = ''; ... dragStarted(event: CdkDragStart) { this.state = 'dragStarted'; } dragEnded(event: CdkDragEnd) { this.state = 'dragEnded'; } dragMoved(event: CdkDragMove) { this.position = `> Position X: ${event.pointerPosition.x} - Y: ${event.pointerPosition.y}`; } }
With a Handle
If we want to restrict the draggable area by a handle element, just add cdkDragHandle
directive to an element inside of cdkDrag
:
<div class="drag-box-with-handler" cdkDrag> <div class="box-handler" cdkDragHandle> gkz </div> </div>
Angular 7 Drag & Drop items in a List
Basic Sorting List
Cover a set of cdkDrag
elements by cdkDropList
will automatically rearrange the list when an element moves. To update that list, we listen cdkDropListDropped
event:
<div class="box-list" cdkDropList (cdkDropListDropped)="drop($event)"> <div class="drag-box" *ngFor="let customer of customers" cdkDrag> {{customer.name}} </div> </div>
We use drag-and-drop CDK moveItemInArray
function to move an item and to calculate the new index of the dropped item inside the array:
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; ... export class SortListComponent implements OnInit { customers = [ { name: 'Adam', age: 23 }, { name: 'Jack', age: 27 }, { name: 'Katherin', age: 26 }, { name: 'John', age: 30 }, { name: 'Watson', age: 42 }, ]; drop(event: CdkDragDrop<string[]>) { moveItemInArray(this.customers, event.previousIndex, event.currentIndex); } }
Horizontal List
By default, cdkDropList
directive make the list vertical. We can change by setting the orientation
property to "horizontal"
.
<div class="box-list-horizontal" cdkDropList cdkDropListOrientation="horizontal" (cdkDropListDropped)="drop($event)"> <div class="drag-box" *ngFor="let customer of customers" cdkDrag> {{customer.name}} </div> </div>
Animations
To set up your animations, we define a transition
that targets transform
property.
drag-and-drop CDK supports animations while:
– sorting an element inside a list => .cdk-drag
.cdk-drag { transition: transform 300ms ease; }
– animating it from the position we dropped it to final place in the list => .cdk-drag-animating
.cdk-drag-animating { transition: transform 300ms ease; }
Placeholder
Default placeholder
Use .cdk-drag-placeholder
to show a placeholder element instead of the real element as it’s being dragged inside a cdkDropList
. By default, this will look exactly like the element that is being sorted.
.cdk-drag-placeholder { background: #ccc; border: dotted 1px #999; transition: transform 500ms ease; }
Custom placeholder
Using *cdkDragPlaceholder
directive, we can replace default placeholder with a custom one:
.box-custom-placeholder { height: 20px; width: 120px; background: #fff; border: dotted 1px #0084ff; transition: transform 200ms ease; } .box-list.cdk-drop-list-dragging .drag-box:not(.cdk-drag-placeholder) { transition: transform 500ms ease; }
With Preview
When a cdkDrag
element is picked up and dragged, a preview element could be visible. By default, the element looks exactly like the element that is being dragged.
We use .cdk-drag-preview
to define preview CSS:
.cdk-drag-preview { box-shadow: 0 3px 3px -3px #0084ff; }
We also need to provide a custom template using *cdkDragPreview
:
<div cdkDropList class="box-list" (cdkDropListDropped)="drop($event)"> <div class="drag-box" *ngFor="let customer of customers" cdkDrag> {{customer.name}} <p *cdkDragPreview>Age: {{customer.age}}</p> </div> </div>
Angular 7 Drag & Drop between Lists
Basic
We can connect one or more cdkDropList
together using cdkDropListConnectedTo
property. Then we set cdkDropListData
and cdkDragData
for associating data with cdkDropList
and cdkDrag
.
<div class="box-list" cdkDropList #inactiveList="cdkDropList" id = "Inactive Customers" [cdkDropListData]="inactiveCustomers" [cdkDropListConnectedTo]="[activeList]" (cdkDropListDropped)="drop($event)"> <div class="drag-box" [cdkDragData]="customer" *ngFor="let customer of inactiveCustomers" cdkDrag> {{customer}} </div> </div>
Implementation of cdkDropListDropped
function will use drag-and-drop CDK moveItemInArray
and transferArrayItem
:
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; ... export class TransferItemsListsComponent { inactiveCustomers = [ 'Adam', 'Jack', 'Katherin' ]; activeCustomers = [ 'John', 'Watson' ]; drop(event: CdkDragDrop<string[]>) { if (event.previousContainer === event.container) { console.log('dropped Event', `> dropped '${event.item.data}' into '${event.container.id}'`); moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); } else { console.log('dropped Event', `> dropped '${event.item.data}' into '${event.container.id}'`); transferArrayItem( event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex); } } }
Event Handler with List
Drag Event Handlers
– cdkDragStarted
: emits when the user starts dragging the item.
– cdkDragEnded
: emits when the user stops dragging the item.
– cdkDragEntered
: emits when the item are moved into a new container.
– cdkDragExited
: emits when dragging the item into another container.
<div class="box-list" cdkDropList #activeList1="cdkDropList" id = "Active Customers 1" [cdkDropListData]="activeCustomers" [cdkDropListConnectedTo]="[inactiveList1]" (cdkDropListDropped)="drop($event)"> <div class="drag-box" [cdkDragData]="customer" *ngFor="let customer of activeCustomers" cdkDrag (cdkDragStarted)="dragStarted($event)" (cdkDragEnded)="dragEnded($event)" (cdkDragEntered)="dragEntered($event)" (cdkDragExited)="dragExited($event)"> {{customer}} </div> </div>
Implementations of the functions:
import { moveItemInArray, transferArrayItem, CdkDragDrop, CdkDragStart, CdkDragEnd, CdkDragEnter, CdkDragExit } from '@angular/cdk/drag-drop'; ... export class TransferItemsListsComponent { ... dragStarted(event: CdkDragStart) { console.log('dragStarted Event > item', event.source.data); } dragEnded(event: CdkDragEnd) { console.log('dragEnded Event > item', event.source.data); } dragEntered(event: CdkDragEnter) { console.log('dragEntered Event', `> dropping '${event.item.data}' into '${event.container.id}'`); } dragExited(event: CdkDragExit) { console.log('dragExited Event', `> drag '${event.item.data}' from '${event.container.id}'`); } }
Drop Event Handlers
– cdkDropListDropped
: emits when an item was dropped inside the container.
– cdkDropListEntered
: emits when a new item is dragged into this container.
– cdkDropListExited
: emits when an item is dragged from this container into another container.
<div class="box-list" cdkDropList #inactiveList2="cdkDropList" id = "Inactive Customers 2" [cdkDropListData]="inactiveCustomers" [cdkDropListConnectedTo]="[activeList2]" (cdkDropListDropped)="drop($event)" (cdkDropListEntered)="dropListEntered($event)" (cdkDropListExited)="dropListExited($event)"> <div class="drag-box" [cdkDragData]="customer" *ngFor="let customer of inactiveCustomers" cdkDrag> {{customer}} </div> </div>
Implementations of the functions:
import { CdkDragEnter, CdkDragExit } from '@angular/cdk/drag-drop'; ... export class TransferItemsListsComponent { drop(event: CdkDragDrop<string[]>) { ... console.log('dropped Event', `> dropped '${event.item.data}' into '${event.container.id}'`); } dropListEntered(event: CdkDragEnter) { console.log('dropListEntered Event', `> dropping '${event.item.data}' into '${event.container.id}'`); } dropListExited(event: CdkDragExit) { console.log('dropListExited Event', `> drag '${event.item.data}' from '${event.container.id}'`); } }
Source Code
AngularMaterialCDK-Drag-Drop-example