이 페이지에서는 다음 기능을 갖춘 드래그 앤 드롭 인터페이스를 빠르게 만들 수 있는 드래그 앤 드롭 디렉티브를 설명합니다.
- 자유로운 드래그
- 순서를 바꿀 수 있는 드래그 가능한 엘리먼트 목록 생성
- 목록 간 드래그 가능한 엘리먼트 이동
- 드래그 애니메이션
- 드래그 가능한 엘리먼트의 이동을 특정 축이나 엘리먼트 안으로 제한
- 커스텀 드래그 핸들 추가
- 드래그 미리보기 추가
- 커스텀 드래그 플레이스홀더 추가
전체 API 참조는 Angular CDK의 드래그 앤 드롭 API 참조 페이지를 참고하세요.
컴포넌트 개발 도구 모음(Component Dev Kit, CDK)은 컴포넌트를 만들기 위한 기본 동작들의 집합입니다.
드래그 앤 드롭 디렉티브를 사용하려면 먼저 npm에서 @angular/cdk를 설치하세요.
터미널에서 Angular CLI로 설치할 수 있습니다.
드래그 앤 드롭을 사용하려면 컴포넌트에서 필요한 디렉티브를 불러오세요.
import {Component} from '@angular/core';
import {CdkDrag} from '@angular/cdk/drag-drop';
@Component({
selector: 'drag-drop-example',
templateUrl: 'drag-drop-example.html',
imports: [CdkDrag],
})
export class DragDropExample {}
엘리먼트에 cdkDrag 디렉티브를 추가하면 어떤 엘리먼트든 드래그할 수 있습니다.
기본적으로 모든 드래그 가능한 엘리먼트는 자유로운 드래그를 지원합니다.
<div class="example-box" cdkDrag>Drag me around</div>
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Basic Drag&Drop
*/
@Component({
selector: 'cdk-drag-drop-overview-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag],
})
export class CdkDragDropOverviewExample {}
.example-box {
width: 200px;
height: 200px;
border: solid 1px #ccc;
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;
font-family: sans-serif;
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);
}
.example-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);
}
부모 엘리먼트에 cdkDropList 디렉티브를 추가하면 드래그 가능한 엘리먼트를 순서를 바꿀 수 있는 모음으로 묶을 수 있습니다.
이 디렉티브는 드래그한 엘리먼트를 놓을 수 있는 영역을 정의합니다.
드롭 목록으로 묶인 드래그 가능한 엘리먼트는 엘리먼트가 이동할 때 자동으로 재배치됩니다.
드래그 앤 드롭 디렉티브는 데이터 모델을 갱신하지 않습니다.
데이터 모델을 갱신하려면 사용자가 드래그를 마쳤을 때 발생하는 cdkDropListDropped 이벤트를 수신하고 데이터 모델을 직접 갱신하세요.
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
@for (movie of movies; track movie) {
<div class="example-box" cdkDrag>{{ movie }}</div>
}
</div>
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop sorting
*/
@Component({
selector: 'cdk-drag-drop-sorting-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropSortingExample {
movies = [
'Episode I - The Phantom Menace',
'Episode II - Attack of the Clones',
'Episode III - Revenge of the Sith',
'Episode IV - A New Hope',
'Episode V - The Empire Strikes Back',
'Episode VI - Return of the Jedi',
'Episode VII - The Force Awakens',
'Episode VIII - The Last Jedi',
'Episode IX - The Rise of Skywalker',
];
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.movies, event.previousIndex, event.currentIndex);
}
}
.example-list {
width: 500px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: block;
background: white;
border-radius: 4px;
overflow: hidden;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
border: none;
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
cdkDropList 인스턴스를 참조하려면 CDK_DROP_LIST 주입 토큰을 사용할 수 있습니다.
자세한 내용은 의존성 주입 가이드와 드롭 목록 주입 토큰 API를 참고하세요.
cdkDropList 디렉티브는 연결된 드롭 목록 사이에서 드래그 가능한 엘리먼트를 이동하는 기능을 지원합니다.
cdkDropList 인스턴스를 서로 연결하는 방법은 두 가지입니다.
cdkDropListConnectedTo 프로퍼티에 다른 드롭 목록을 지정합니다.
cdkDropListGroup 어트리뷰트가 있는 엘리먼트로 해당 엘리먼트들을 감쌉니다.
cdkDropListConnectedTo 디렉티브는 다른 cdkDropList를 직접 참조하거나 다른 드롭 컨테이너의 id를 참조하는 방식 모두 지원합니다.
<!-- 올바른 사용법입니다. -->
<div cdkDropList #listOne="cdkDropList" [cdkDropListConnectedTo]="[listTwo]"></div>
<div cdkDropList #listTwo="cdkDropList" [cdkDropListConnectedTo]="[listOne]"></div>
<!-- 이 방법도 올바른 사용법입니다. -->
<div cdkDropList id="list-one" [cdkDropListConnectedTo]="['list-two']"></div>
<div cdkDropList id="list-two" [cdkDropListConnectedTo]="['list-one']"></div>
<div class="example-container">
<h2>To do</h2>
<div
cdkDropList
#todoList="cdkDropList"
[cdkDropListData]="todo"
[cdkDropListConnectedTo]="[doneList]"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (item of todo; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
</div>
<div class="example-container">
<h2>Done</h2>
<div
cdkDropList
#doneList="cdkDropList"
[cdkDropListData]="done"
[cdkDropListConnectedTo]="[todoList]"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (item of done; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDropList,
moveItemInArray,
transferArrayItem,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop connected sorting
*/
@Component({
selector: 'cdk-drag-drop-connected-sorting-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropConnectedSortingExample {
todo = ['Get to work', 'Pick up groceries', 'Go home', 'Fall asleep'];
done = ['Get up', 'Brush teeth', 'Take a shower', 'Check e-mail', 'Walk dog'];
drop(event: CdkDragDrop<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,
);
}
}
}
.example-container {
width: 400px;
max-width: 100%;
margin: 0 25px 25px 0;
display: inline-block;
vertical-align: top;
}
.example-list {
border: solid 1px #ccc;
min-height: 60px;
background: white;
border-radius: 4px;
overflow: hidden;
display: block;
font-family: sans-serif;
}
h2 {
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
연결할 드롭 목록의 개수를 미리 알 수 없다면 cdkDropListGroup 디렉티브로 연결을 자동 설정하세요.
그룹 아래에 새로 추가되는 모든 cdkDropList는 다른 모든 목록과 자동으로 연결됩니다.
<div cdkDropListGroup>
<div class="example-container">
<h2>To do</h2>
<div
cdkDropList
[cdkDropListData]="todo"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (item of todo; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
</div>
<div class="example-container">
<h2>Done</h2>
<div
cdkDropList
[cdkDropListData]="done"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (item of done; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
</div>
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDropList,
CdkDropListGroup,
moveItemInArray,
transferArrayItem,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop connected sorting group
*/
@Component({
selector: 'cdk-drag-drop-connected-sorting-group-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropListGroup, CdkDropList, CdkDrag],
})
export class CdkDragDropConnectedSortingGroupExample {
todo = ['Get to work', 'Pick up groceries', 'Go home', 'Fall asleep'];
done = ['Get up', 'Brush teeth', 'Take a shower', 'Check e-mail', 'Walk dog'];
drop(event: CdkDragDrop<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,
);
}
}
}
.example-container {
width: 400px;
max-width: 100%;
margin: 0 25px 25px 0;
display: inline-block;
vertical-align: top;
}
.example-list {
border: solid 1px #ccc;
min-height: 60px;
background: white;
border-radius: 4px;
overflow: hidden;
display: block;
font-family: sans-serif;
}
h2 {
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
cdkDropListGroup 인스턴스를 참조하려면 CDK_DROP_LIST_GROUP 주입 토큰을 사용할 수 있습니다.
자세한 내용은 의존성 주입 가이드와 드롭 목록 그룹 주입 토큰 API를 참고하세요.
기본적으로 사용자는 cdkDrag 엘리먼트를 한 컨테이너에서 연결된 다른 컨테이너로 옮길 수 있습니다.
어떤 엘리먼트를 컨테이너에 놓을 수 있는지 더 세밀하게 제어하려면 cdkDropListEnterPredicate를 사용하세요.
드래그 가능한 엘리먼트가 새 컨테이너에 들어올 때마다 Angular가 조건 함수를 호출합니다.
이 함수가 true 또는 false 중 무엇을 반환하는지에 따라 항목을 새 컨테이너에 넣을 수 있는지가 결정됩니다.
<div class="example-container">
<h2>Available numbers</h2>
<div
id="all"
cdkDropList
[cdkDropListData]="all"
cdkDropListConnectedTo="even"
class="example-list"
(cdkDropListDropped)="drop($event)"
[cdkDropListEnterPredicate]="noReturnPredicate"
>
@for (number of all; track number) {
<div class="example-box" [cdkDragData]="number" cdkDrag>{{ number }}</div>
}
</div>
</div>
<div class="example-container">
<h2>Even numbers</h2>
<div
id="even"
cdkDropList
[cdkDropListData]="even"
cdkDropListConnectedTo="all"
class="example-list"
(cdkDropListDropped)="drop($event)"
[cdkDropListEnterPredicate]="evenPredicate"
>
@for (number of even; track number) {
<div class="example-box" cdkDrag [cdkDragData]="number">{{ number }}</div>
}
</div>
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDropList,
moveItemInArray,
transferArrayItem,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop enter predicate
*/
@Component({
selector: 'cdk-drag-drop-enter-predicate-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropEnterPredicateExample {
all = [1, 2, 3, 4, 5, 6, 7, 8, 9];
even = [10];
drop(event: CdkDragDrop<number[]>) {
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,
);
}
}
/** Predicate function that only allows even numbers to be dropped into a list. */
evenPredicate(item: CdkDrag<number>) {
return item.data % 2 === 0;
}
/** Predicate function that doesn't allow items to be dropped into a list. */
noReturnPredicate() {
return false;
}
}
.example-container {
width: 400px;
max-width: 100%;
margin: 0 25px 25px 0;
display: inline-block;
vertical-align: top;
}
.example-list {
border: solid 1px #ccc;
min-height: 60px;
background: white;
border-radius: 4px;
overflow: hidden;
display: block;
}
h2 {
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
cdkDragData와 cdkDropListData를 각각 설정하면 cdkDrag와 cdkDropList에 임의의 데이터를 연결할 수 있습니다.
두 디렉티브에서 발생하는 이벤트에는 이 데이터가 포함되므로, 이벤트에 바인딩하면 드래그나 드롭 상호작용이 어디에서 시작되었는지 쉽게 파악할 수 있습니다.
@for (list of lists; track list) {
<div cdkDropList [cdkDropListData]="list" (cdkDropListDropped)="drop($event)">
@for (item of list; track item) {
<div cdkDrag [cdkDragData]="item"></div>
}
</div>
}
기본적으로 사용자는 cdkDrag 엘리먼트의 어느 부분을 잡아도 드래그해 이동할 수 있습니다.
핸들 엘리먼트로만 드래그하도록 제한하려면 cdkDrag 안의 엘리먼트에 cdkDragHandle 디렉티브를 추가하세요.
cdkDragHandle 엘리먼트는 원하는 만큼 추가할 수 있습니다.
<div class="example-box" cdkDrag>
I can only be dragged using the handle
<div class="example-handle" cdkDragHandle>
<svg width="24px" fill="currentColor" viewBox="0 0 24 24">
<path
d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"
></path>
<path d="M0 0h24v24H0z" fill="none"></path>
</svg>
</div>
</div>
import {CdkDrag, CdkDragHandle} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop with a handle
*/
@Component({
selector: 'cdk-drag-drop-handle-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag, CdkDragHandle],
})
export class CdkDragDropHandleExample {}
.example-box {
width: 200px;
height: 200px;
padding: 10px;
box-sizing: border-box;
border: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
justify-content: center;
align-items: center;
text-align: center;
background: #fff;
border-radius: 4px;
position: relative;
z-index: 1;
font-family: sans-serif;
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);
}
.example-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);
}
.example-handle {
position: absolute;
top: 10px;
right: 10px;
color: #ccc;
cursor: move;
width: 24px;
height: 24px;
}
cdkDragHandle 인스턴스를 참조하려면 CDK_DRAG_HANDLE 주입 토큰을 사용할 수 있습니다.
자세한 내용은 의존성 주입 가이드와 드래그 핸들 주입 토큰 API를 참고하세요.
cdkDrag 엘리먼트를 드래그하면 미리보기 엘리먼트가 나타납니다.
기본 미리보기는 원본 엘리먼트의 복제본이며 사용자 커서 옆에 배치됩니다.
미리보기를 커스터마이징하려면 *cdkDragPreview로 커스텀 템플릿을 제공하세요.
엘리먼트의 내용에 대해 가정하지 않으므로 커스텀 미리보기는 드래그하는 원본 엘리먼트와 크기가 일치하지 않습니다.
드래그 미리보기의 크기를 원본 엘리먼트에 맞추려면 matchSize 입력값에 true를 전달하세요.
같은 id를 가진 엘리먼트가 페이지에 여러 개 존재하지 않도록 복제된 엘리먼트에서는 id 어트리뷰트가 제거됩니다.
따라서 해당 id를 대상으로 하는 CSS는 적용되지 않습니다.
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
@for (movie of movies; track movie) {
<div class="example-box" cdkDrag>
{{ movie.title }}
<img *cdkDragPreview [src]="movie.poster" [alt]="movie.title" />
</div>
}
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDragPreview,
CdkDropList,
moveItemInArray,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop custom preview
*/
@Component({
selector: 'cdk-drag-drop-custom-preview-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag, CdkDragPreview],
})
export class CdkDragDropCustomPreviewExample {
// tslint:disable:max-line-length
movies = [
{
title: 'Episode I - The Phantom Menace',
poster: 'https://upload.wikimedia.org/wikipedia/en/4/40/Star_Wars_Phantom_Menace_poster.jpg',
},
{
title: 'Episode II - Attack of the Clones',
poster:
'https://upload.wikimedia.org/wikipedia/en/3/32/Star_Wars_-_Episode_II_Attack_of_the_Clones_%28movie_poster%29.jpg',
},
{
title: 'Episode III - Revenge of the Sith',
poster:
'https://upload.wikimedia.org/wikipedia/en/9/93/Star_Wars_Episode_III_Revenge_of_the_Sith_poster.jpg',
},
{
title: 'Episode IV - A New Hope',
poster: 'https://upload.wikimedia.org/wikipedia/en/8/87/StarWarsMoviePoster1977.jpg',
},
{
title: 'Episode V - The Empire Strikes Back',
poster:
'https://upload.wikimedia.org/wikipedia/en/3/3f/The_Empire_Strikes_Back_%281980_film%29.jpg',
},
{
title: 'Episode VI - Return of the Jedi',
poster: 'https://upload.wikimedia.org/wikipedia/en/b/b2/ReturnOfTheJediPoster1983.jpg',
},
{
title: 'Episode VII - The Force Awakens',
poster:
'https://upload.wikimedia.org/wikipedia/en/a/a2/Star_Wars_The_Force_Awakens_Theatrical_Poster.jpg',
},
{
title: 'Episode VIII - The Last Jedi',
poster: 'https://upload.wikimedia.org/wikipedia/en/7/7f/Star_Wars_The_Last_Jedi.jpg',
},
{
title: 'Episode IX – The Rise of Skywalker',
poster:
'https://upload.wikimedia.org/wikipedia/en/a/af/Star_Wars_The_Rise_of_Skywalker_poster.jpg',
},
];
// tslint:enable:max-line-length
drop(event: CdkDragDrop<{title: string; poster: string}[]>) {
moveItemInArray(this.movies, event.previousIndex, event.currentIndex);
}
}
.example-list {
width: 500px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: block;
background: white;
border-radius: 4px;
overflow: hidden;
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
cdkDragPreview 인스턴스를 참조하려면 CDK_DRAG_PREVIEW 주입 토큰을 사용할 수 있습니다.
자세한 내용은 의존성 주입 가이드와 드래그 미리보기 주입 토큰 API를 참고하세요.
Angular는 기본적으로 위치 지정과 오버플로 문제를 피하기 위해 페이지의 <body>에 cdkDrag 미리보기를 삽입합니다.
하지만 이 경우 미리보기에 상속된 스타일이 적용되지 않으므로 적절하지 않을 수 있습니다.
cdkDrag의 cdkDragPreviewContainer 입력값으로 Angular가 미리보기를 삽입할 위치를 변경할 수 있습니다.
사용 가능한 값은 다음과 같습니다.
또는 값이 global이나 parent인 경우, CDK_DRAG_CONFIG 주입 토큰을 수정해 설정의 previewContainer를 갱신할 수 있습니다.
자세한 내용은 의존성 주입 가이드, 드래그 설정 주입 토큰 API, 드래그 앤 드롭 설정 API를 참고하세요.
cdkDrag 엘리먼트를 드래그하는 동안 디렉티브는 엘리먼트를 놓을 위치를 보여주는 플레이스홀더 엘리먼트를 생성합니다.
기본 플레이스홀더는 드래그하는 엘리먼트의 복제본입니다.
*cdkDragPlaceholder 디렉티브로 커스텀 플레이스홀더를 사용할 수 있습니다.
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
@for (movie of movies; track movie) {
<div class="example-box" cdkDrag>
<div class="example-custom-placeholder" *cdkDragPlaceholder></div>
{{ movie }}
</div>
}
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDragPlaceholder,
CdkDropList,
moveItemInArray,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop custom placeholder
*/
@Component({
selector: 'cdk-drag-drop-custom-placeholder-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag, CdkDragPlaceholder],
})
export class CdkDragDropCustomPlaceholderExample {
movies = [
'Episode I - The Phantom Menace',
'Episode II - Attack of the Clones',
'Episode III - Revenge of the Sith',
'Episode IV - A New Hope',
'Episode V - The Empire Strikes Back',
'Episode VI - Return of the Jedi',
'Episode VII - The Force Awakens',
'Episode VIII - The Last Jedi',
'Episode IX - The Rise of Skywalker',
];
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.movies, event.previousIndex, event.currentIndex);
}
}
.example-list {
width: 500px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: block;
background: white;
border-radius: 4px;
overflow: hidden;
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-custom-placeholder {
background: #ccc;
border: dotted 3px #999;
min-height: 60px;
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
cdkDragPlaceholder 인스턴스를 참조하려면 CDK_DRAG_PLACEHOLDER 주입 토큰을 사용할 수 있습니다.
자세한 내용은 의존성 주입 가이드와 드래그 플레이스홀더 주입 토큰 API를 참고하세요.
드래그할 수 있게 만들려는 엘리먼트에 직접 접근할 수 없다면 cdkDragRootElement 어트리뷰트를 설정하세요.
이 어트리뷰트는 선택자를 받아 일치하는 엘리먼트를 찾을 때까지 DOM의 상위 방향으로 탐색합니다.
일치하는 엘리먼트를 찾으면 해당 엘리먼트를 드래그할 수 있게 됩니다.
대화상자를 드래그할 수 있게 만드는 등의 경우에 유용합니다.
<button type="button" (click)="openDialog()">Open a draggable dialog</button>
<ng-template>
<div class="example-dialog-content" cdkDrag cdkDragRootElement=".cdk-overlay-pane">
Drag the dialog around!
</div>
</ng-template>
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Overlay, OverlayRef} from '@angular/cdk/overlay';
import {TemplatePortal} from '@angular/cdk/portal';
import {
AfterViewInit,
Component,
OnDestroy,
TemplateRef,
ViewContainerRef,
inject,
viewChild,
} from '@angular/core';
/**
* @title Drag&Drop with alternate root element
*/
@Component({
selector: 'cdk-drag-drop-root-element-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag],
})
export class CdkDragDropRootElementExample implements AfterViewInit, OnDestroy {
private _overlay = inject(Overlay);
private _viewContainerRef = inject(ViewContainerRef);
private _dialogTemplate = viewChild.required(TemplateRef);
private _overlayRef!: OverlayRef;
private _portal!: TemplatePortal;
ngAfterViewInit() {
this._portal = new TemplatePortal(this._dialogTemplate(), this._viewContainerRef);
this._overlayRef = this._overlay.create({
positionStrategy: this._overlay.position().global().centerHorizontally().centerVertically(),
hasBackdrop: true,
});
this._overlayRef.backdropClick().subscribe(() => this._overlayRef.detach());
}
ngOnDestroy() {
this._overlayRef.dispose();
}
openDialog() {
this._overlayRef.attach(this._portal);
}
}
.example-dialog-content {
width: 200px;
height: 200px;
border: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
cursor: move;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
border-radius: 4px;
font-family: sans-serif;
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);
}
.example-dialog-content: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);
}
또는 CDK_DRAG_CONFIG 주입 토큰을 수정해 설정의 rootElementSelector를 갱신할 수 있습니다.
자세한 내용은 의존성 주입 가이드, 드래그 설정 주입 토큰 API, 드래그 앤 드롭 설정 API를 참고하세요.
기본적으로 cdkDropList 안에 있지 않은 cdkDrag 엘리먼트는 사용자가 직접 옮길 때만 일반적인 DOM 위치에서 이동합니다.
엘리먼트의 위치를 명시적으로 설정하려면 cdkDragFreeDragPosition 입력값을 사용하세요.
사용자가 다른 페이지로 이동했다가 돌아왔을 때 드래그 가능한 엘리먼트의 위치를 복원하는 것이 대표적인 사용 사례입니다.
<p>
<button type="button" (click)="changePosition()">Change element position</button>
</p>
<div class="example-box" cdkDrag [cdkDragFreeDragPosition]="dragPosition">Drag me around</div>
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Programmatically setting the free drag position
*/
@Component({
selector: 'cdk-drag-drop-free-drag-position-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag],
})
export class CdkDragDropFreeDragPositionExample {
dragPosition = {x: 0, y: 0};
changePosition() {
this.dragPosition = {x: this.dragPosition.x + 50, y: this.dragPosition.y + 50};
}
}
.example-box {
width: 200px;
height: 200px;
border: solid 1px #ccc;
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;
font-family: sans-serif;
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);
}
.example-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);
}
사용자가 cdkDrag 엘리먼트를 다른 엘리먼트 밖으로 드래그하지 못하게 하려면 cdkDragBoundary 어트리뷰트에 CSS 선택자를 전달하세요.
이 어트리뷰트는 선택자를 받아 일치하는 엘리먼트를 찾을 때까지 DOM의 상위 방향으로 탐색합니다.
일치하는 엘리먼트를 찾으면 해당 엘리먼트가 드래그 가능한 엘리먼트가 벗어날 수 없는 경계가 됩니다.
cdkDrag가 cdkDropList 안에 있는 경우에도 cdkDragBoundary를 사용할 수 있습니다.
<div class="example-boundary">
<div class="example-box" cdkDragBoundary=".example-boundary" cdkDrag>
I can only be dragged within the dotted container
</div>
</div>
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop boundary
*/
@Component({
selector: 'cdk-drag-drop-boundary-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag],
})
export class CdkDragDropBoundaryExample {}
.example-box {
width: 200px;
height: 200px;
border: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
cursor: move;
display: inline-flex;
justify-content: center;
align-items: center;
text-align: center;
background: #fff;
border-radius: 4px;
margin-right: 25px;
position: relative;
z-index: 1;
box-sizing: border-box;
padding: 10px;
font-family: sans-serif;
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);
}
.example-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);
}
.example-boundary {
width: 400px;
height: 400px;
max-width: 100%;
border: dotted #ccc 2px;
}
또는 CDK_DRAG_CONFIG 주입 토큰을 수정해 설정의 boundaryElement를 갱신할 수 있습니다.
자세한 내용은 의존성 주입 가이드, 드래그 설정 주입 토큰 API, 드래그 앤 드롭 설정 API를 참고하세요.
기본적으로 cdkDrag는 모든 방향으로 자유롭게 이동할 수 있습니다.
드래그를 특정 축으로 제한하려면 cdkDrag의 cdkDragLockAxis를 "x" 또는 "y"로 설정하세요.
cdkDropList 안의 여러 드래그 가능한 엘리먼트에 같은 제한을 적용하려면 cdkDropList에 cdkDropListLockAxis를 설정하세요.
<div class="example-box" cdkDragLockAxis="y" cdkDrag>I can only be dragged up/down</div>
<div class="example-box" cdkDragLockAxis="x" cdkDrag>I can only be dragged left/right</div>
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop position locking
*/
@Component({
selector: 'cdk-drag-drop-axis-lock-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag],
})
export class CdkDragDropAxisLockExample {}
.example-box {
width: 200px;
height: 200px;
border: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
cursor: move;
display: inline-flex;
justify-content: center;
align-items: center;
text-align: center;
background: #fff;
border-radius: 4px;
margin-right: 25px;
position: relative;
z-index: 1;
font-family: sans-serif;
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);
}
.example-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);
}
또는 CDK_DRAG_CONFIG 주입 토큰을 수정해 설정의 lockAxis를 갱신할 수 있습니다.
자세한 내용은 의존성 주입 가이드, 드래그 설정 주입 토큰 API, 드래그 앤 드롭 설정 API를 참고하세요.
기본적으로 사용자가 cdkDrag 위에서 포인터를 누르면 드래그 과정이 시작됩니다.
터치 기기에서 전체 화면을 차지하는 드래그 가능한 엘리먼트처럼, 사용자가 페이지를 스크롤하다가 실수로 드래그 이벤트를 발생시킬 수 있는 경우에는 이 동작이 적절하지 않을 수 있습니다.
cdkDragStartDelay 입력값으로 드래그 시작을 지연할 수 있습니다.
이 입력값은 사용자가 지정된 밀리초만큼 포인터를 누른 상태를 유지한 후에 엘리먼트 드래그를 시작하도록 합니다.
<div class="example-box" cdkDrag [cdkDragStartDelay]="1000">Dragging starts after one second</div>
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Delay dragging
*/
@Component({
selector: 'cdk-drag-drop-delay-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDrag],
})
export class CdkDragDropDelayExample {}
.example-box {
width: 200px;
height: 200px;
border: solid 1px #ccc;
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;
font-family: sans-serif;
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);
}
.example-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);
}
또는 CDK_DRAG_CONFIG 주입 토큰을 수정해 설정의 dragStartDelay를 갱신할 수 있습니다.
자세한 내용은 의존성 주입 가이드, 드래그 설정 주입 토큰 API, 드래그 앤 드롭 설정 API를 참고하세요.
특정 항목의 드래그 활성화 여부를 바꾸려면 cdkDrag 항목의 cdkDragDisabled 입력값을 true 또는 false로 설정하세요.
cdkDropList의 cdkDropListDisabled 입력값으로 목록 전체를 비활성화할 수 있습니다.
cdkDragHandle의 cdkDragHandleDisabled로 특정 핸들만 비활성화할 수도 있습니다.
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
@for (item of items; track item) {
<div class="example-box" cdkDrag [cdkDragDisabled]="item.disabled">{{ item.value }}</div>
}
</div>
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop disabled
*/
@Component({
selector: 'cdk-drag-drop-disabled-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropDisabledExample {
items = [
{value: 'I can be dragged', disabled: false},
{value: 'I cannot be dragged', disabled: true},
{value: 'I can also be dragged', disabled: false},
];
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.items, event.previousIndex, event.currentIndex);
}
}
.example-list {
width: 500px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: block;
background: white;
border-radius: 4px;
overflow: hidden;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.example-box.cdk-drag-disabled {
background: #ccc;
cursor: not-allowed;
user-select: none;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
또는 CDK_DRAG_CONFIG 주입 토큰을 수정해 설정의 draggingDisabled를 갱신할 수 있습니다.
자세한 내용은 의존성 주입 가이드, 드래그 설정 주입 토큰 API, 드래그 앤 드롭 설정 API를 참고하세요.
기본적으로 cdkDropList 디렉티브는 목록이 세로 방향이라고 가정합니다.
cdkDropListOrientation 프로퍼티를 horizontal로 설정하면 이를 변경할 수 있습니다.
<div
cdkDropList
cdkDropListOrientation="horizontal"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (timePeriod of timePeriods; track timePeriod) {
<div class="example-box" cdkDrag>{{ timePeriod }}</div>
}
</div>
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop horizontal sorting
*/
@Component({
selector: 'cdk-drag-drop-horizontal-sorting-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropHorizontalSortingExample {
timePeriods = [
'Bronze age',
'Iron age',
'Middle ages',
'Early modern period',
'Long nineteenth century',
];
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.timePeriods, event.previousIndex, event.currentIndex);
}
}
.example-list {
width: 1000px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: flex;
flex-direction: row;
background: white;
border-radius: 4px;
overflow: hidden;
}
.example-box {
padding: 20px 10px;
border-right: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
flex-grow: 1;
flex-basis: 0;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
또는 CDK_DRAG_CONFIG 주입 토큰을 수정해 설정의 listOrientation을 갱신할 수 있습니다.
자세한 내용은 의존성 주입 가이드, 드래그 설정 주입 토큰 API, 드래그 앤 드롭 설정 API를 참고하세요.
기본적으로 cdkDropList는 CSS transform으로 드래그 가능한 엘리먼트를 이동해 정렬합니다.
이 방식은 정렬에 애니메이션을 적용할 수 있어 사용자 경험을 개선합니다.
다만 드롭 목록이 세로 또는 가로 중 한 방향으로만 동작한다는 단점이 있습니다.
정렬 가능한 목록이 여러 줄로 이어져야 한다면 cdkDropListOrientation 어트리뷰트를 mixed로 설정할 수 있습니다.
그러면 목록은 DOM에서 엘리먼트를 실제로 이동하는 다른 정렬 전략을 사용합니다.
하지만 이 경우 정렬 동작에 애니메이션을 적용할 수 없습니다.
<div
cdkDropList
cdkDropListOrientation="mixed"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (item of items; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop horizontal wrapping list
*/
@Component({
selector: 'cdk-drag-drop-mixed-sorting-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropMixedSortingExample {
items = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine'];
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.items, event.previousIndex, event.currentIndex);
}
}
.example-list {
display: flex;
flex-wrap: wrap;
width: 505px;
max-width: 100%;
gap: 15px;
padding: 15px;
border: solid 1px #ccc;
min-height: 60px;
border-radius: 4px;
overflow: hidden;
}
.example-box {
padding: 20px 10px;
border: solid 1px #ccc;
border-radius: 4px;
color: rgba(0, 0, 0, 0.87);
display: inline-block;
box-sizing: border-box;
cursor: move;
background: white;
text-align: center;
font-size: 14px;
min-width: 115px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
기본적으로 cdkDrag 엘리먼트는 cdkDropList 안의 어느 위치로든 정렬할 수 있습니다.
이 동작을 변경하려면 함수를 받는 cdkDropListSortPredicate 어트리뷰트를 설정하세요.
드래그 가능한 엘리먼트를 드롭 목록의 새 인덱스로 이동하려고 할 때마다 조건 함수를 호출합니다.
함수가 true를 반환하면 항목을 새 인덱스로 이동하고, 그렇지 않으면 현재 위치를 유지합니다.
<div
cdkDropList
class="example-list"
(cdkDropListDropped)="drop($event)"
[cdkDropListSortPredicate]="sortPredicate"
>
@for (number of numbers; track number) {
<div class="example-box" [cdkDragData]="number" cdkDrag>{{ number }}</div>
}
</div>
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop sort predicate
*/
@Component({
selector: 'cdk-drag-drop-sort-predicate-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropSortPredicateExample {
numbers = [1, 2, 3, 4, 5, 6, 7, 8];
drop(event: CdkDragDrop<unknown>) {
moveItemInArray(this.numbers, event.previousIndex, event.currentIndex);
}
/**
* Predicate function that only allows even numbers to be
* sorted into even indices and odd numbers at odd indices.
*/
sortPredicate(index: number, item: CdkDrag<number>) {
return (index + 1) % 2 === item.data % 2;
}
}
.example-list {
border: solid 1px #ccc;
min-height: 60px;
background: white;
border-radius: 4px;
overflow: hidden;
display: block;
width: 400px;
max-width: 100%;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
드래그 가능한 엘리먼트를 한 cdkDropList에서 다른 목록으로 옮길 수는 있지만, 원본 목록 안에서는 사용자가 순서를 바꾸지 못하게 해야 하는 경우가 있습니다.
이때는 cdkDropListSortingDisabled 어트리뷰트를 추가해 cdkDropList 안의 드래그 가능한 엘리먼트가 정렬되지 않게 하세요.
이렇게 하면 엘리먼트를 새로운 유효한 위치로 드래그하지 않았을 때 원본 목록의 처음 위치가 유지됩니다.
<div cdkDropListGroup>
<div class="example-container">
<h2>Available items</h2>
<div
cdkDropList
[cdkDropListData]="items"
class="example-list"
cdkDropListSortingDisabled
(cdkDropListDropped)="drop($event)"
>
@for (item of items; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
</div>
<div class="example-container">
<h2>Shopping basket</h2>
<div
cdkDropList
[cdkDropListData]="basket"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (item of basket; track item) {
<div class="example-box" cdkDrag>{{ item }}</div>
}
</div>
</div>
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDropList,
CdkDropListGroup,
moveItemInArray,
transferArrayItem,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop disabled sorting
*/
@Component({
selector: 'cdk-drag-drop-disabled-sorting-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropListGroup, CdkDropList, CdkDrag],
})
export class CdkDragDropDisabledSortingExample {
items = ['Carrots', 'Tomatoes', 'Onions', 'Apples', 'Avocados'];
basket = ['Oranges', 'Bananas', 'Cucumbers'];
drop(event: CdkDragDrop<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,
);
}
}
}
.example-container {
width: 400px;
max-width: 100%;
margin: 0 25px 25px 0;
display: inline-block;
vertical-align: top;
}
.example-list {
border: solid 1px #ccc;
min-height: 60px;
background: white;
border-radius: 4px;
overflow: hidden;
display: block;
}
h2 {
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-placeholder {
opacity: 0;
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
또는 CDK_DRAG_CONFIG 주입 토큰을 수정해 설정의 sortingDisabled를 갱신할 수 있습니다.
자세한 내용은 의존성 주입 가이드, 드래그 설정 주입 토큰 API, 드래그 앤 드롭 설정 API를 참고하세요.
기본적으로 항목을 한 목록에서 다른 목록으로 드래그하면 원래 목록에서 제거되어 이동합니다.
하지만 디렉티브를 설정해 원본 목록의 항목을 유지하면서 복사할 수도 있습니다.
복사를 활성화하려면 cdkDropListHasAnchor 입력값을 설정하세요.
그러면 cdkDropList는 항목과 함께 이동하지 않고 원래 컨테이너에 남아 있는 "앵커" 엘리먼트를 생성합니다.
사용자가 항목을 원래 컨테이너로 다시 옮기면 앵커는 자동으로 제거됩니다.
.cdk-drag-anchor CSS 클래스로 앵커 엘리먼트의 스타일을 지정할 수 있습니다.
cdkDropListHasAnchor와 cdkDropListSortingDisabled를 함께 사용하면 원본 목록의 순서를 바꾸지 않으면서 항목을 복사할 수 있는 목록을 만들 수 있습니다(예: 상품 목록과 장바구니).
<div class="example-container">
<h2>Products</h2>
<div
cdkDropList
[cdkDropListData]="products"
[cdkDropListConnectedTo]="[cartList]"
cdkDropListSortingDisabled
cdkDropListHasAnchor
class="example-list"
>
@for (product of products; track $index) {
<div class="example-box" cdkDrag [cdkDragData]="product">{{ product }}</div>
}
</div>
</div>
<div class="example-container">
<h2>Shopping cart</h2>
<div
cdkDropList
#cartList="cdkDropList"
[cdkDropListData]="cart"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
@for (product of cart; track $index) {
<div class="example-box" cdkDrag>{{ product }}</div>
}
</div>
</div>
import {
CdkDrag,
CdkDragDrop,
CdkDropList,
copyArrayItem,
moveItemInArray,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';
/**
* @title Drag&Drop copy between lists
*/
@Component({
selector: 'cdk-drag-drop-copy-list-example',
templateUrl: 'app.html',
styleUrl: 'app.css',
imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropCopyListExample {
products = ['Bananas', 'Oranges', 'Bread', 'Butter', 'Soda', 'Eggs'];
cart = ['Tomatoes'];
drop(event: CdkDragDrop<string[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
copyArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex,
);
}
}
}
.example-container {
width: 400px;
max-width: 100%;
margin: 0 25px 25px 0;
display: inline-block;
vertical-align: top;
font-family: sans-serif;
}
.example-list {
border: solid 1px #ccc;
min-height: 60px;
background: white;
border-radius: 4px;
overflow: hidden;
display: block;
}
h2 {
font-family: sans-serif;
}
.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
font-family: sans-serif;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
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);
}
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
.example-box:last-child {
border: none;
}
.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
드래그 앤 드롭은 다음 두 동작에 애니메이션을 지원합니다.
- 목록 안에서 드래그 가능한 엘리먼트 정렬
- 사용자가 놓은 위치에서 목록 안의 최종 위치까지 드래그 가능한 엘리먼트 이동
애니메이션을 설정하려면 transform 프로퍼티를 대상으로 하는 CSS 트랜지션을 정의하세요.
애니메이션에 사용할 수 있는 클래스는 다음과 같습니다.
cdkDrag와 cdkDropList 디렉티브는 기능 동작에 꼭 필요한 스타일만 적용합니다.
애플리케이션에서는 다음 CSS 클래스를 대상으로 스타일을 커스터마이징할 수 있습니다.
드래그 가능한 항목이 스크롤 가능한 컨테이너(예: overflow: auto가 지정된 div) 안에 있다면, 해당 컨테이너에 cdkScrollable 디렉티브가 있어야 자동 스크롤이 동작합니다.
이 디렉티브가 없으면 CDK는 드래그하는 동안 컨테이너의 스크롤 동작을 감지하거나 제어할 수 없습니다.
CDK의 드래그 앤 드롭 기능은 다양한 컴포넌트와 통합할 수 있습니다.
대표적인 사용 사례로는 정렬 가능한 MatTable 컴포넌트와 정렬 가능한 MatTabGroup 컴포넌트가 있습니다.