آنلاین

Drag and drop

Overview

این صفحه directiveهای drag and drop را توضیح می‌دهد که به شما اجازه می‌دهند interfaceهای drag and drop را سریع با موارد زیر بسازید:

  • Free dragging
  • ساخت listای از elementهای draggable قابل reorder
  • انتقال elementهای draggable بین listها
  • Dragging animationها
  • قفل کردن elementهای draggable روی یک axis یا element
  • افزودن drag handleهای سفارشی
  • افزودن preview هنگام drag
  • افزودن drag placeholder سفارشی

برای reference کامل API، صفحه drag and drop API reference در Angular CDK را ببینید.

پیش از شروع

نصب CDK

Component Dev Kit (CDK) مجموعه‌ای از primitiveهای رفتاری برای ساخت کامپوننت‌هاست. برای استفاده از directiveهای drag and drop، ابتدا @angular/cdk را از npm نصب کنید. می‌توانید این کار را از terminal با Angular CLI انجام دهید:

shell
ng add @angular/cdk

Import کردن drag and drop

برای استفاده از drag and drop، چیزهایی را که نیاز دارید از directiveها در کامپوننت خود import کنید.

ts
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 {}

ساخت elementهای draggable

می‌توانید هر elementای را با افزودن directive مربوط به cdkDrag draggable کنید. به صورت پیش‌فرض، همه elementهای draggable از free dragging پشتیبانی می‌کنند.

html
<div class="example-box" cdkDrag>Drag me around</div>
ts
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 {}
css
.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);
}

ساخت listای از elementهای draggable قابل reorder

directive مربوط به cdkDropList را به یک parent element اضافه کنید تا elementهای draggable را در یک collection قابل reorder گروه‌بندی کند. این مشخص می‌کند elementهای draggable کجا می‌توانند drop شوند. elementهای draggable داخل drop list group هنگام حرکت یک element به صورت خودکار rearrange می‌شوند.

directiveهای drag and drop، data model شما را update نمی‌کنند. برای update کردن data model، به event مربوط به cdkDropListDropped گوش دهید \(وقتی کاربر dragging را تمام می‌کند\) و data model را دستی update کنید.

html
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
  @for (movie of movies; track movie) {
    <div class="example-box" cdkDrag>{{ movie }}</div>
  }
</div>
ts
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);
  }
}
css
.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);
}

می‌توانید از injection token مربوط به CDKDROPLIST استفاده کنید که برای reference گرفتن از instanceهای cdkDropList به کار می‌رود. برای اطلاعات بیشتر، راهنمای dependency injection و drop list injection token API را ببینید.

انتقال elementهای draggable بین listها

directive مربوط به cdkDropList از انتقال elementهای draggable بین drop listهای connected پشتیبانی می‌کند. دو راه برای connect کردن یک یا چند instance از cdkDropList به هم وجود دارد:

  • property مربوط به cdkDropListConnectedTo را روی drop list دیگری تنظیم کنید.
  • elementها را داخل elementای با attribute مربوط به cdkDropListGroup wrap کنید.

directive مربوط به cdkDropListConnectedTo هم با reference مستقیم به cdkDropList دیگر کار می‌کند و هم با reference به id مربوط به drop container دیگر.

html
<!-- This is valid -->
<div cdkDropList #listOne="cdkDropList" [cdkDropListConnectedTo]="[listTwo]"></div>
<div cdkDropList #listTwo="cdkDropList" [cdkDropListConnectedTo]="[listOne]"></div>

<!-- This is valid as well -->
<div cdkDropList id="list-one" [cdkDropListConnectedTo]="['list-two']"></div>
<div cdkDropList id="list-two" [cdkDropListConnectedTo]="['list-one']"></div>
html
<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>
ts
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,
      );
    }
  }
}
css
.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);
}

اگر تعداد نامشخصی drop list connected دارید، از directive مربوط به cdkDropListGroup استفاده کنید تا connection به صورت خودکار setup شود. هر cdkDropList جدیدی که زیر یک group اضافه شود، خودکار به همه listهای دیگر connect می‌شود.

html
<div cdkDropListGroup>
  <!-- All lists in here will be connected. -->
  @for (list of lists; track list) {
    <div cdkDropList></div>
  }
</div>
html
<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>
ts
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,
      );
    }
  }
}
css
.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);
}

می‌توانید از injection token مربوط به CDKDROPLIST_GROUP استفاده کنید که برای reference گرفتن از instanceهای cdkDropListGroup به کار می‌رود. برای اطلاعات بیشتر، راهنمای dependency injection و drop list group injection token API را ببینید.

Selective dragging

به صورت پیش‌فرض، کاربر می‌تواند elementهای cdkDrag را از یک container به container connected دیگر منتقل کند. برای کنترل دقیق‌تر اینکه کدام elementها بتوانند داخل یک container drop شوند، از cdkDropListEnterPredicate استفاده کنید. Angular هر بار که یک element draggable وارد container جدید می‌شود، predicate را فراخوانی می‌کند. بسته به اینکه predicate مقدار true یا false برگرداند، item ممکن است اجازه ورود به container جدید را داشته باشد یا نداشته باشد.

html
<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>
ts
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;
  }
}
css
.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);
}

Attach کردن data

می‌توانید با تنظیم cdkDragData یا cdkDropListData به ترتیب، data دلخواهی را با هر دو cdkDrag و cdkDropList associate کنید. می‌توانید به eventهایی که از هر دو directive fire می‌شوند bind کنید؛ این eventها شامل همین data هستند و به شما اجازه می‌دهند origin مربوط به drag یا drop interaction را به سادگی تشخیص دهید.

html
@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>
}

سفارشی‌سازی Dragging

سفارشی‌سازی drag handle

به صورت پیش‌فرض، کاربر می‌تواند کل element مربوط به cdkDrag را drag کند تا آن را جابه‌جا کند. برای محدود کردن کاربر به اینکه فقط با یک handle element بتواند این کار را انجام دهد، directive مربوط به cdkDragHandle را به elementای داخل cdkDrag اضافه کنید. می‌توانید هر تعداد element از نوع cdkDragHandle که می‌خواهید داشته باشید.

html
<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>
ts
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 {}
css
.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;
}

می‌توانید از injection token مربوط به CDKDRAGHANDLE استفاده کنید که برای reference گرفتن از instanceهای cdkDragHandle به کار می‌رود. برای اطلاعات بیشتر، راهنمای dependency injection و drag handle injection token API را ببینید.

سفارشی‌سازی drag preview

وقتی یک element مربوط به cdkDrag در حال drag شدن است، یک preview element visible می‌شود. به صورت پیش‌فرض، preview یک clone از element اصلی است که کنار cursor کاربر position شده است.

برای سفارشی‌سازی preview، یک template سفارشی از طریق *cdkDragPreview فراهم کنید. preview سفارشی با اندازه element اصلی dragged match نمی‌شود، چون فرضی درباره content element در نظر گرفته نمی‌شود. برای match کردن اندازه element برای drag preview، مقدار true را به input مربوط به matchSize پاس دهید.

element cloneشده attribute مربوط به id خودش را حذف می‌کند تا چند element با id یکسان در صفحه وجود نداشته باشد. این باعث می‌شود هر CSSای که آن id را target می‌کند اعمال نشود.

html
<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>
ts
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);
  }
}
css
.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);
}

می‌توانید از injection token مربوط به CDKDRAGPREVIEW استفاده کنید که برای reference گرفتن از instanceهای cdkDragPreview به کار می‌رود. برای اطلاعات بیشتر، راهنمای dependency injection و drag preview injection token API را ببینید.

سفارشی‌سازی drag insertion point

به صورت پیش‌فرض، Angular برای جلوگیری از issueهای positioning و overflow، preview مربوط به cdkDrag را داخل <body> صفحه insert می‌کند. این ممکن است در بعضی caseها مطلوب نباشد، چون preview styleهای inherited خود را دریافت نمی‌کند.

می‌توانید محل insert شدن preview توسط Angular را با input مربوط به cdkDragPreviewContainer روی cdkDrag تغییر دهید. valueهای ممکن:

ValueDescriptionAdvantagesDisadvantages
globalمقدار پیش‌فرض. Angular preview را داخل <body> یا نزدیک‌ترین shadow root insert می‌کند.preview تحت تأثیر z-index یا overflow: hidden قرار نمی‌گیرد. همچنین روی selectorهای :nth-child و flex layoutها اثر نمی‌گذارد.styleهای inherited را حفظ نمی‌کند.
parentAngular preview را داخل parent همان elementای که drag می‌شود insert می‌کند.preview همان styleهای element dragged را inherit می‌کند.preview ممکن است با overflow: hidden clip شود یا به دلیل z-index زیر elementهای دیگر قرار بگیرد. علاوه بر این، می‌تواند روی selectorهای :nth-child و بعضی flex layoutها اثر بگذارد.
ElementRef یا HTMLElementAngular preview را داخل element مشخص‌شده insert می‌کند.preview styleها را از container element مشخص‌شده inherit می‌کند.preview ممکن است با overflow: hidden clip شود یا به دلیل z-index زیر elementهای دیگر قرار بگیرد. علاوه بر این، می‌تواند روی selectorهای :nth-child و بعضی flex layoutها اثر بگذارد.

به جای آن، می‌توانید injection token مربوط به CDKDRAGCONFIG را modify کنید تا اگر value برابر global یا parent است، previewContainer را داخل config update کنید. برای اطلاعات بیشتر، راهنمای dependency injection، drag config injection token API و drag drop config API را ببینید.

سفارشی‌سازی drag placeholder

وقتی یک element مربوط به cdkDrag در حال drag شدن است، directive یک placeholder element ایجاد می‌کند که نشان می‌دهد element هنگام drop شدن کجا قرار خواهد گرفت. به صورت پیش‌فرض، placeholder یک clone از elementای است که drag می‌شود. می‌توانید با directive مربوط به *cdkDragPlaceholder آن را با نمونه سفارشی جایگزین کنید:

html
<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>
ts
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);
  }
}
css
.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);
}

می‌توانید از injection token مربوط به CDKDRAGPLACEHOLDER استفاده کنید که برای reference گرفتن از instanceهای cdkDragPlaceholder به کار می‌رود. برای اطلاعات بیشتر، راهنمای dependency injection و drag placeholder injection token API را ببینید.

سفارشی‌سازی drag root element

اگر elementای هست که می‌خواهید draggable شود اما به آن دسترسی مستقیم ندارید، attribute مربوط به cdkDragRootElement را تنظیم کنید.

این attribute یک selector می‌پذیرد و در DOM به سمت بالا جستجو می‌کند تا elementای را پیدا کند که با selector match شود. اگر element پیدا شود، draggable می‌شود. این برای caseهایی مثل draggable کردن dialog مفید است.

html
<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>
ts
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);
  }
}
css
.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);
}

به جای آن، می‌توانید injection token مربوط به CDKDRAGCONFIG را modify کنید تا rootElementSelector را داخل config update کنید. برای اطلاعات بیشتر، راهنمای dependency injection، drag config injection token API و drag drop config API را ببینید.

تنظیم position در DOM برای یک element draggable

به صورت پیش‌فرض، elementهای cdkDrag که داخل cdkDropList نیستند فقط وقتی کاربر element را دستی جابه‌جا کند از position عادی خود در DOM حرکت می‌کنند. از input مربوط به cdkDragFreeDragPosition استفاده کنید تا position element را صریح تنظیم کنید. یک use case رایج برای این کار، restore کردن position یک element draggable بعد از این است که کاربر از صفحه خارج شده و سپس برگشته است.

html
<p>
  <button type="button" (click)="changePosition()">Change element position</button>
</p>

<div class="example-box" cdkDrag [cdkDragFreeDragPosition]="dragPosition">Drag me around</div>
ts
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};
  }
}
css
.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);
}

محدود کردن movement داخل یک element

برای اینکه کاربر نتواند یک element مربوط به cdkDrag را بیرون از element دیگری drag کند، یک CSS selector را به attribute مربوط به cdkDragBoundary پاس دهید. این attribute یک selector می‌پذیرد و در DOM به سمت بالا جستجو می‌کند تا elementای را پیدا کند که با آن match شود. اگر match پیدا شود، آن element به boundary تبدیل می‌شود که element draggable نمی‌تواند بیرون از آن drag شود. cdkDragBoundary همچنین وقتی cdkDrag داخل cdkDropList قرار دارد قابل استفاده است.

html
<div class="example-boundary">
  <div class="example-box" cdkDragBoundary=".example-boundary" cdkDrag>
    I can only be dragged within the dotted container
  </div>
</div>
ts
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 {}
css
.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;
}

به جای آن، می‌توانید injection token مربوط به CDKDRAGCONFIG را modify کنید تا boundaryElement را داخل config update کنید. برای اطلاعات بیشتر، راهنمای dependency injection، drag config injection token API و drag drop config API را ببینید.

محدود کردن movement روی یک axis

به صورت پیش‌فرض، cdkDrag حرکت آزاد در همه جهت‌ها را اجازه می‌دهد. برای محدود کردن dragging به یک axis مشخص، cdkDragLockAxis را روی cdkDrag به مقدار "x" یا "y" تنظیم کنید. برای محدود کردن dragging چند element draggable داخل cdkDropList، به جای آن cdkDropListLockAxis را روی cdkDropList تنظیم کنید.

html
<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>
ts
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 {}
css
.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);
}

به جای آن، می‌توانید injection token مربوط به CDKDRAGCONFIG را modify کنید تا lockAxis را داخل config update کنید. برای اطلاعات بیشتر، راهنمای dependency injection، drag config injection token API و drag drop config API را ببینید.

Delay دادن به dragging

به صورت پیش‌فرض وقتی کاربر pointer خود را روی یک cdkDrag پایین می‌آورد، sequence مربوط به dragging شروع می‌شود. این behavior ممکن است در caseهایی مثل elementهای fullscreen draggable روی touch deviceها مطلوب نباشد، جایی که کاربر ممکن است هنگام scroll کردن صفحه تصادفی drag event را trigger کند.

می‌توانید sequence مربوط به dragging را با input مربوط به cdkDragStartDelay delay دهید. این input منتظر می‌ماند تا کاربر pointer را به تعداد millisecond مشخص‌شده نگه دارد و سپس element را drag کند.

html
<div class="example-box" cdkDrag [cdkDragStartDelay]="1000">Dragging starts after one second</div>
ts
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 {}
css
.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);
}

به جای آن، می‌توانید injection token مربوط به CDKDRAGCONFIG را modify کنید تا dragStartDelay را داخل config update کنید. برای اطلاعات بیشتر، راهنمای dependency injection، drag config injection token API و drag drop config API را ببینید.

غیرفعال کردن dragging

اگر می‌خواهید dragging را برای یک drag item مشخص غیرفعال کنید، input مربوط به cdkDragDisabled را روی یک item از نوع cdkDrag به true یا false تنظیم کنید. می‌توانید کل list را با input مربوط به cdkDropListDisabled روی یک cdkDropList غیرفعال کنید. همچنین غیرفعال کردن یک handle مشخص از طریق cdkDragHandleDisabled روی cdkDragHandle ممکن است.

html
<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>
ts
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);
  }
}
css
.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);
}

به جای آن، می‌توانید injection token مربوط به CDKDRAGCONFIG را modify کنید تا draggingDisabled را داخل config update کنید. برای اطلاعات بیشتر، راهنمای dependency injection، drag config injection token API و drag drop config API را ببینید.

سفارشی‌سازی Sorting

جهت list

به صورت پیش‌فرض، directive مربوط به cdkDropList فرض می‌کند listها vertical هستند. می‌توان این را با تنظیم property مربوط به cdkDropListOrientation به horizontal تغییر داد.

html
<div
  cdkDropList
  cdkDropListOrientation="horizontal"
  class="example-list"
  (cdkDropListDropped)="drop($event)"
>
  @for (timePeriod of timePeriods; track timePeriod) {
    <div class="example-box" cdkDrag>{{ timePeriod }}</div>
  }
</div>
ts
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);
  }
}
css
.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);
}

به جای آن، می‌توانید injection token مربوط به CDKDRAGCONFIG را modify کنید تا listOrientation را داخل config update کنید. برای اطلاعات بیشتر، راهنمای dependency injection، drag config injection token API و drag drop config API را ببینید.

Wrapping در list

به صورت پیش‌فرض، cdkDropList elementهای draggable را با جابه‌جا کردن آن‌ها توسط CSS transform sort می‌کند. این امکان animated شدن sorting را فراهم می‌کند و تجربه کاربری بهتری می‌دهد. اما یک drawback هم دارد: drop list فقط در یک جهت کار می‌کند، vertical یا horizontal.

اگر sortable listای دارید که باید روی خط‌های جدید wrap شود، می‌توانید attribute مربوط به cdkDropListOrientation را روی mixed تنظیم کنید. این باعث می‌شود list از strategy متفاوتی برای sort کردن elementها استفاده کند که شامل جابه‌جا کردن آن‌ها در DOM است. با این حال، list دیگر نمی‌تواند action مربوط به sorting را animate کند.

html
<div
  cdkDropList
  cdkDropListOrientation="mixed"
  class="example-list"
  (cdkDropListDropped)="drop($event)"
>
  @for (item of items; track item) {
    <div class="example-box" cdkDrag>{{ item }}</div>
  }
</div>
ts
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);
  }
}
css
.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);
}

Selective sorting

به صورت پیش‌فرض، elementهای cdkDrag در هر positionای داخل یک cdkDropList sort می‌شوند. برای تغییر این behavior، attribute مربوط به cdkDropListSortPredicate را تنظیم کنید که یک function می‌گیرد. predicate function هر بار که یک element draggable قرار است به index جدیدی داخل drop list منتقل شود فراخوانی می‌شود. اگر predicate مقدار true برگرداند، item به index جدید منتقل می‌شود؛ در غیر این صورت position فعلی خود را نگه می‌دارد.

html
<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>
ts
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;
  }
}
css
.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);
}

غیرفعال کردن sorting

caseهایی وجود دارد که elementهای draggable می‌توانند از یک cdkDropList به دیگری drag شوند، اما کاربر نباید بتواند آن‌ها را داخل source list sort کند. برای این caseها، attribute مربوط به cdkDropListSortingDisabled را اضافه کنید تا جلوی sort شدن elementهای draggable داخل یک cdkDropList گرفته شود. اگر dragged element به یک position معتبر جدید drag نشود، این کار position اولیه آن در source list را حفظ می‌کند.

html
<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>
ts
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,
      );
    }
  }
}
css
.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);
}

به جای آن، می‌توانید injection token مربوط به CDKDRAGCONFIG را modify کنید تا sortingDisabled را داخل config update کنید. برای اطلاعات بیشتر، راهنمای dependency injection، drag config injection token API و drag drop config API را ببینید.

Copy کردن itemها بین listها

به صورت پیش‌فرض، وقتی یک item از یک list به list دیگر drag می‌شود، از list اصلی خودش منتقل می‌شود. اما می‌توانید directiveها را طوری configure کنید که item copy شود و item اصلی در source list باقی بماند.

برای فعال کردن copying، می‌توانید input مربوط به cdkDropListHasAnchor را تنظیم کنید. این به cdkDropList می‌گوید یک element از نوع "anchor" بسازد که در container اصلی می‌ماند و همراه item حرکت نمی‌کند. اگر کاربر item را به container اصلی برگرداند، anchor به صورت خودکار حذف می‌شود. anchor element را می‌توان با target کردن CSS class مربوط به .cdk-drag-anchor style داد.

ترکیب cdkDropListHasAnchor با cdkDropListSortingDisabled ساختن listای را ممکن می‌کند که کاربر بتواند itemها را از آن copy کند بدون اینکه بتواند source list را reorder کند \(مثلاً product list و shopping cart\).

html
<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>
ts
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,
      );
    }
  }
}
css
.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);
}

سفارشی‌سازی animationها

Drag and drop برای هر دو مورد زیر از animation پشتیبانی می‌کند:

  • Sort کردن یک element draggable داخل list
  • حرکت دادن element draggable از positionای که کاربر آن را drop کرده تا position نهایی داخل list

برای setup کردن animationهای خود، یک CSS transition تعریف کنید که property مربوط به transform را target کند. classهای زیر برای animationها قابل استفاده هستند:

CSS class nameنتیجه افزودن transition
.cdk-dragelementهای draggable را هنگام sort شدن animate می‌کند.
.cdk-drag-animatingelement draggable را از position dropشده به position نهایی داخل cdkDropList animate می‌کند.

این CSS class فقط وقتی action مربوط به dragging متوقف شده روی یک element از نوع cdkDrag اعمال می‌شود.

Styling

هر دو directive مربوط به cdkDrag و cdkDropList فقط styleهای ضروری لازم برای functionality را اعمال می‌کنند. Applicationها می‌توانند با target کردن این CSS classهای مشخص، styleهای خود را سفارشی کنند.

CSS class nameDescription
.cdk-drop-listselector برای container elementهای cdkDropList.
.cdk-dragselector برای elementهای cdkDrag.
.cdk-drag-disabledselector برای elementهای disabled از نوع cdkDrag.
.cdk-drag-handleselector برای host element مربوط به cdkDragHandle.
.cdk-drag-previewselector برای drag preview element. این همان elementای است که هنگام drag کردن یک element در sortable list، کنار cursor کاربر ظاهر می‌شود.

این element دقیقاً شبیه elementای است که drag می‌شود، مگر اینکه با template سفارشی از طریق *cdkDragPreview سفارشی شده باشد.
.cdk-drag-placeholderselector برای drag placeholder element. این همان elementای است که در spotای نمایش داده می‌شود که draggable element پس از پایان action مربوط به dragging به آنجا drag خواهد شد.

این element دقیقاً شبیه elementای است که sort می‌شود، مگر اینکه با directive مربوط به cdkDragPlaceholder سفارشی شده باشد.
.cdk-drop-list-draggingselector برای container element مربوط به cdkDropList که در حال حاضر یک draggable element در حال drag شدن دارد.
.cdk-drop-list-disabledselector برای container elementهای cdkDropList که disabled هستند.
.cdk-drop-list-receivingselector برای container element مربوط به cdkDropList که یک draggable element دارد که می‌تواند از یک connected drop list که در حال drag شدن است دریافت کند.
.cdk-drag-anchorselector برای anchor elementای که وقتی cdkDropListHasAnchor فعال است ایجاد می‌شود. این element position شروع item dragged را نشان می‌دهد.

Dragging داخل scrollable container

اگر draggable itemهای شما داخل یک scrollable container هستند \(مثلاً یک div با overflow: auto\)، automatic scrolling کار نمی‌کند مگر اینکه scrollable container، directive مربوط به cdkScrollable را داشته باشد. بدون آن، CDK نمی‌تواند scroll behavior مربوط به container را هنگام drag operationها detect یا control کند.

Integration با کامپوننت‌های دیگر

قابلیت drag-and-drop در CDK می‌تواند با کامپوننت‌های مختلف integrate شود. use caseهای رایج شامل کامپوننت‌های sortable از نوع MatTable و کامپوننت‌های sortable از نوع MatTabGroup هستند.