How to Drag and Drop in Angular
We will introduce the @angular/cdk/drag-drop
module to accomplish drag and drop in angular.
We will also introduce some examples for drag and drop in angular.
Drag and Drop in Angular
The @angular/cdk/drag-drop
module provides you with a way to easily and declaratively create drag & drop interfaces. This module supports free dragging, sorting within a list, transferring items between lists, animations, touch devices, custom drag handles, previews, and placeholders.
Getting Started
First of all, we will import DragDropModule
into NgModule
in app.module.ts
.
Here is the code for app.module.ts
.
# angular
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { DragDropModule } from "@angular/cdk/drag-drop";
import { AppComponent } from "./app.component";
@NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule,
CommonModule,
DragDropModule
],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
Now we will import the following code to our app.component.ts
.
# angular
import { Component, NgModule, ViewChild } from "@angular/core";
import {
CdkDrag,
CdkDragStart,
CdkDropList,
CdkDropListGroup,
CdkDragMove,
CdkDragEnter,
moveItemInArray
} from "@angular/cdk/drag-drop";
import { ViewportRuler } from "@angular/cdk/overlay";
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
Now we will export our class AppComponent
and define variables in app.component.ts
# angular
export class AppComponent {
@ViewChild(CdkDropListGroup) listGroup: CdkDropListGroup<CdkDropList>;
@ViewChild(CdkDropList) placeholder: CdkDropList;
public items: Array<number> = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
public target: CdkDropList;
public targetIndex: number;
public source: CdkDropList;
public sourceIndex: number;
public dragIndex: number;
public activeContainer;
constructor(private viewportRuler: ViewportRuler) {
this.target = null;
this.source = null;
}
Now we will add after view Initial function in app.component.ts
# angular
ngAfterViewInit() {
let phElement = this.placeholder.element.nativeElement;
phElement.style.display = "none";
phElement.parentElement.removeChild(phElement);
}
Now we will create an add()
function to app.component.ts
to add elements in a list.
# angular
add() {
this.items.push(this.items.length + 1);
}
Now we will create a function to shuffle our list in app.component.ts
.
#angular
shuffle() {
this.items.sort(function() {
return 0.5 - Math.random();
});
}
Now we will create the dragMoved
function, which drags the element in app.component.ts
.
# angular
dragMoved(e: CdkDragMove) {
let point = this.getPointerPositionOnPage(e.event);
this.listGroup._items.forEach(dropList => {
if (__isInsideDropListClientRect(dropList, point.x, point.y)) {
this.activeContainer = dropList;
return;
}
});
}
Now we will create the dropListDropped
function in app.component.ts
, which will drop the element after releasing it.
# angular
dropListDropped() {
if (!this.target) return;
let phElement = this.placeholder.element.nativeElement;
let parent = phElement.parentElement;
phElement.style.display = "none";
parent.removeChild(phElement);
parent.appendChild(phElement);
parent.insertBefore(
this.source.element.nativeElement,
parent.children[this.sourceIndex]
);
this.target = null;
this.source = null;
if (this.sourceIndex != this.targetIndex)
moveItemInArray(this.items, this.sourceIndex, this.targetIndex);
console.log("save here!", this.items);
}
Now we will get the point of the page that the user touched before dropping the element.
We will add getPointerPositionOnPage
function in app.component.ts
# angular
getPointerPositionOnPage(event: MouseEvent | TouchEvent) {
const point = __isTouchEvent(event)
? event.touches[0] || event.changedTouches[0]
: event;
const scrollPosition = this.viewportRuler.getViewportScrollPosition();
return {
x: point.pageX - scrollPosition.left,
y: point.pageY - scrollPosition.top
};
}
Now we will add the onClick
function, which will be performed when the user clicks on list elements.
# angular
onClick(event) {
console.log(event);
alert("click!");
}
Now we will add the following functions to app.component.ts
.
function __indexOf(collection, node) {
return Array.prototype.indexOf.call(collection, node);
}
function __isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent {
return event.type.startsWith("touch");
}
function __isInsideDropListClientRect(
dropList: CdkDropList,
x: number,
y: number
) {
const {
top,
bottom,
left,
right
} = dropList.element.nativeElement.getBoundingClientRect();
return y >= top && y <= bottom && x >= left && x <= right;
}
So, our app.component.ts
file will look like below.
# angular
import { Component, NgModule, ViewChild } from "@angular/core";
import {
CdkDrag,
CdkDragStart,
CdkDropList,
CdkDropListGroup,
CdkDragMove,
CdkDragEnter,
moveItemInArray
} from "@angular/cdk/drag-drop";
import { ViewportRuler } from "@angular/cdk/overlay";
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
@ViewChild(CdkDropListGroup) listGroup: CdkDropListGroup<CdkDropList>;
@ViewChild(CdkDropList) placeholder: CdkDropList;
public items: Array<number> = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
public target: CdkDropList;
public targetIndex: number;
public source: CdkDropList;
public sourceIndex: number;
public dragIndex: number;
public activeContainer;
constructor(private viewportRuler: ViewportRuler) {
this.target = null;
this.source = null;
}
ngAfterViewInit() {
let phElement = this.placeholder.element.nativeElement;
phElement.style.display = "none";
phElement.parentElement.removeChild(phElement);
}
add() {
this.items.push(this.items.length + 1);
}
shuffle() {
this.items.sort(function() {
return 0.5 - Math.random();
});
}
dragMoved(e: CdkDragMove) {
let point = this.getPointerPositionOnPage(e.event);
this.listGroup._items.forEach(dropList => {
if (__isInsideDropListClientRect(dropList, point.x, point.y)) {
this.activeContainer = dropList;
return;
}
});
}
dropListDropped() {
if (!this.target) return;
let phElement = this.placeholder.element.nativeElement;
let parent = phElement.parentElement;
phElement.style.display = "none";
parent.removeChild(phElement);
parent.appendChild(phElement);
parent.insertBefore(
this.source.element.nativeElement,
parent.children[this.sourceIndex]
);
this.target = null;
this.source = null;
if (this.sourceIndex != this.targetIndex)
moveItemInArray(this.items, this.sourceIndex, this.targetIndex);
console.log("save here!", this.items);
}
dropListEnterPredicate = (drag: CdkDrag, drop: CdkDropList) => {
if (drop == this.placeholder) return true;
if (drop != this.activeContainer) return false;
let phElement = this.placeholder.element.nativeElement;
let sourceElement = drag.dropContainer.element.nativeElement;
let dropElement = drop.element.nativeElement;
let dragIndex = __indexOf(
dropElement.parentElement.children,
this.source ? phElement : sourceElement
);
let dropIndex = __indexOf(dropElement.parentElement.children, dropElement);
if (!this.source) {
this.sourceIndex = dragIndex;
this.source = drag.dropContainer;
phElement.style.width = sourceElement.clientWidth + "px";
phElement.style.height = sourceElement.clientHeight + "px";
sourceElement.parentElement.removeChild(sourceElement);
}
this.targetIndex = dropIndex;
this.target = drop;
phElement.style.display = "";
dropElement.parentElement.insertBefore(
phElement,
dropIndex > dragIndex ? dropElement.nextSibling : dropElement
);
this.placeholder._dropListRef.enter(
drag._dragRef,
drag.element.nativeElement.offsetLeft,
drag.element.nativeElement.offsetTop
);
return false;
};
const point = __isTouchEvent(event)
? event.touches[0] || event.changedTouches[0]
: event;
const scrollPosition = this.viewportRuler.getViewportScrollPosition();
return {
x: point.pageX - scrollPosition.left,
y: point.pageY - scrollPosition.top
};
}
onClick(event) {
console.log(event);
alert("click!");
}
}
function __indexOf(collection, node) {
return Array.prototype.indexOf.call(collection, node);
}
function __isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent {
return event.type.startsWith("touch");
}
function __isInsideDropListClientRect(
dropList: CdkDropList,
x: number,
y: number
) {
const {
top,
bottom,
left,
right
} = dropList.element.nativeElement.getBoundingClientRect();
return y >= top && y <= bottom && x >= left && x <= right;
}
Now we will create a frontend and add the following code in app.component.html
.
<h1>Drag&Drop with a flex-wrap</h1>
<button (click)="add()">Add</button> <button (click)="shuffle()">Shuffle</button
><br />
<ul class="angular-list">
<li *ngFor="let item of items">{{ item }}</li>
</ul>
<div class="amgular-container" cdkDropListGroup>
<div
cdkDropList
[cdkDropListEnterPredicate]="dropListEnterPredicate"
(cdkDropListDropped)="dropListDropped()"
></div>
<div
cdkDropList
*ngFor="let item of items"
[cdkDropListEnterPredicate]="dropListEnterPredicate"
(cdkDropListDropped)="dropListDropped()"
>
<div
cdkDrag
class="angular-box"
(cdkDragMoved)="dragMoved($event)"
(click)="onClick($event)"
>
{{ item }}
</div>
</div>
</div>
And we will add our styling code in app.component.css
.
# angular
.angular-list {
list-style-type: none;
padding: 0;
}
.angular-list li {
display: table-cell;
padding: 4px;
}
.angular-container {
display: flex;
flex-wrap: wrap;
}
.angular-box {
width: 200px;
height: 200px;
border: solid 1px #ccc;
font-size: 30pt;
font-weight: bold;
color: rgba(0, 0, 0, 0.87);
cursor: move;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
background: #fff;
border-radius: 4px;
position: relative;
z-index: 1;
transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12);
}
.angular-box:active {
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);
opacity: 0.6;
}
.cdk-drop-list {
display: flex;
padding-right: 15px;
padding-bottom: 15px;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
box-shadow: 0 5px 5px -3px rgba(161, 41, 41, 0.2),
0 8px 10px 1px rgba(141, 58, 58, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
.cdk-drag-placeholder {
opacity: 0.5;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
button {
margin-right: 4px;
}
Now our output will look like this.
Output:
Rana is a computer science graduate passionate about helping people to build and diagnose scalable web application problems and problems developers face across the full-stack.
LinkedIn