Angular에서 드래그 앤 드롭
Angular에서 드래그 앤 드롭을 수행하는 @angular/cdk/drag-drop
모듈을 소개합니다.
또한 Angular에서 드래그 앤 드롭에 대한 몇 가지 예를 소개합니다.
Angular에서 드래그 앤 드롭
@angular/cdk/drag-drop
모듈은 드래그 앤 드롭 인터페이스를 쉽고 선언적으로 생성하는 방법을 제공합니다. 이 모듈은 자유 끌기, 목록 내 정렬, 목록 간 항목 전송, 애니메이션, 터치 장치, 사용자 정의 끌기 핸들, 미리보기 및 자리 표시자를 지원합니다.
시작하기
먼저 DragDropModule
을 app.module.ts
의 NgModule
로 가져옵니다.
다음은 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 {}
이제 다음 코드를 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"]
})
이제 AppComponent
클래스를 내보내고 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;
}
이제 app.component.ts
에 초기 기능을 추가합니다.
# angular
ngAfterViewInit() {
let phElement = this.placeholder.element.nativeElement;
phElement.style.display = "none";
phElement.parentElement.removeChild(phElement);
}
이제 목록에 요소를 추가하기 위해 app.component.ts
에 add()
함수를 생성합니다.
# angular
add() {
this.items.push(this.items.length + 1);
}
이제 app.component.ts
에서 목록을 섞는 함수를 만들 것입니다.
#angular
shuffle() {
this.items.sort(function() {
return 0.5 - Math.random();
});
}
이제 app.component.ts
의 요소를 드래그하는 dragMoved
함수를 생성합니다.
# 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;
}
});
}
이제 app.component.ts
에 dropListDropped
함수를 생성하여 릴리스 후 요소를 삭제합니다.
# 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);
}
이제 요소를 놓기 전에 사용자가 터치한 페이지의 지점을 얻을 것입니다.
app.component.ts
에 getPointerPositionOnPage
기능을 추가합니다.
# 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
};
}
이제 사용자가 목록 요소를 클릭할 때 수행되는 onClick
기능을 추가합니다.
# angular
onClick(event) {
console.log(event);
alert("click!");
}
이제 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;
}
따라서 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"]
})
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;
}
이제 프론트엔드를 만들고 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>
그리고 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;
}
이제 우리의 출력은 다음과 같을 것입니다.
출력:
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