Как реализовать drag-and-drop с Angular CDK?angular-77

Angular CDK предоставляет мощный модуль DragDrop для реализации функциональности перетаскивания элементов. Рассмотрим полный процесс настройки и использования.

1. Установка и импорт модуля

Сначала необходимо импортировать модуль DragDropModule:

import { DragDropModule } from '@angular/cdk/drag-drop';

@NgModule({
  imports: [
    DragDropModule
  ]
})
export class AppModule { }

2. Базовый пример перетаскивания

Минимальная реализация для перетаскивания элементов внутри одного списка:

<div cdkDropList class="list" (cdkDropListDropped)="drop($event)">
  <div *ngFor="let item of items" cdkDrag class="item">
    {{item}}
  </div>
</div>
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';

items = ['Item 1', 'Item 2', 'Item 3', 'Item 4'];

drop(event: CdkDragDrop<string[]>) {
  moveItemInArray(this.items, event.previousIndex, event.currentIndex);
}
.list {
  width: 200px;
  border: 1px solid #ccc;
  padding: 10px;
}

.item {
  padding: 10px;
  margin: 5px 0;
  background: #f5f5f5;
  cursor: move;
}

3. Перетаскивание между списками

Для реализации перетаскивания между разными списками:

<div class="container">
  <div cdkDropList #todoList="cdkDropList" [cdkDropListData]="todo"
       [cdkDropListConnectedTo]="[doneList]" class="list"
       (cdkDropListDropped)="drop($event)">
    <div *ngFor="let item of todo" cdkDrag class="item">{{item}}</div>
  </div>

  <div cdkDropList #doneList="cdkDropList" [cdkDropListData]="done"
       [cdkDropListConnectedTo]="[todoList]" class="list"
       (cdkDropListDropped)="drop($event)">
    <div *ngFor="let item of done" cdkDrag class="item">{{item}}</div>
  </div>
</div>
import { transferArrayItem } from '@angular/cdk/drag-drop';

todo = ['Task 1', 'Task 2', 'Task 3'];
done = ['Task 4'];

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
    );
  }
}

4. Кастомизация поведения

Ограничение перемещения

<div cdkDropList [cdkDropListEnterPredicate]="noWeekendsPredicate">
noWeekendsPredicate = (drag: CdkDrag, drop: CdkDropList) => {
  return !drag.data.includes('weekend');
}

Задержка перед началом перетаскивания

<div cdkDrag [cdkDragStartDelay]="200">...</div>

Ограничение оси перемещения

<div cdkDrag [cdkDragLockAxis]="'x'">...</div>

5. События Drag-and-Drop

Основные события:

  • cdkDragStarted - начало перетаскивания
  • cdkDragReleased - окончание перетаскивания
  • cdkDragEntered - вход в зону drop
  • cdkDragExited - выход из зоны drop
  • cdkDragDropped - завершение drop

Пример использования:

<div cdkDrag (cdkDragStarted)="onDragStart($event)"
           (cdkDragEnded)="onDragEnd($event)">
  Drag me!
</div>

6. Визуальные эффекты

Превью при перетаскивании

<div cdkDrag>
  <div *cdkDragPreview>{{item.name}}</div>
  <div class="item-content">{{item.name}}</div>
</div>

Placeholder при перетаскивании

<div cdkDropList>
  <div *cdkDragPlaceholder class="placeholder"></div>
  <div *ngFor="let item of items" cdkDrag>{{item}}</div>
</div>
.placeholder {
  background: #ccc;
  border: dotted 3px #999;
  min-height: 60px;
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

7. Работа с комплексными данными

Пример с объектами:

items = [
  { id: 1, name: 'Item 1' },
  { id: 2, name: 'Item 2' }
];

drop(event: CdkDragDrop<any[]>) {
  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
    );
  }
}

8. Оптимизация производительности

  1. Используйте trackBy с *ngFor:
<div *ngFor="let item of items; trackBy: trackById" cdkDrag>
trackById(index: number, item: any): number {
  return item.id;
}
  1. Для больших списков ограничьте перерисовку с помощью OnPush:
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})

9. Интеграция с формами

Для работы с реактивными формами:

this.form = this.fb.group({
  items: this.fb.array(this.items.map(item => this.fb.control(item)))
});

onDrop() {
  this.form.get('items').setValue(this.items);
}

Резюмируем

Angular CDK предоставляет полный набор инструментов для реализации drag-and-drop с поддержкой всех необходимых сценариев - от простого переупорядочивания элементов до сложных взаимодействий между несколькими списками с кастомизацией поведения и визуальных эффектов.