Angular директивы

Для работы с библиотекой Shopify Draggable в Angular оптимальным подходом является использование директив, которые инкапсулируют логику перетаскивания и управления событиями, сохраняя компонент чистым и удобным для повторного использования.


Установка и подключение Draggable

Первым шагом необходимо установить библиотеку через npm:

npm install @shopify/draggable

Импортировать основные модули в Angular можно через ES6 модули:

import { Draggable, Sortable, Swappable } from '@shopify/draggable';

Для работы с Angular директивами важно избегать глобальных селекторов и использовать ViewChild или ElementRef для привязки к конкретным элементам DOM.


Создание директивы Draggable

Директива отвечает за инициализацию экземпляра Draggable и управление его жизненным циклом.

import { Directive, ElementRef, Input, OnInit, OnDestroy } from '@angular/core';
import { Draggable } from '@shopify/draggable';

@Directive({
  selector: '[appDraggable]'
})
export class DraggableDirective implements OnInit, OnDestroy {
  @Input() draggableOptions: any;
  private draggableInstance: Draggable;

  constructor(private el: ElementRef) {}

  ngOnInit(): void {
    this.draggableInstance = new Draggable(this.el.nativeElement, this.draggableOptions || {});
    this.bindEvents();
  }

  private bindEvents(): void {
    this.draggableInstance.on('drag:start', (event) => console.log('Drag started', event));
    this.draggableInstance.on('drag:move', (event) => console.log('Dragging', event));
    this.draggableInstance.on('drag:stop', (event) => console.log('Drag stopped', event));
  }

  ngOnDestroy(): void {
    if (this.draggableInstance) {
      this.draggableInstance.destroy();
    }
  }
}

Ключевые моменты:

  • ElementRef обеспечивает безопасный доступ к DOM элементу.
  • @Input() draggableOptions позволяет настраивать поведение Draggable напрямую из шаблона.
  • События drag:start, drag:move, drag:stop позволяют реализовать кастомные реакции на перетаскивание.
  • Метод destroy() гарантирует очистку ресурсов при удалении компонента.

Директива Sortable

Для сортировки элементов лучше создать отдельную директиву, которая наследует принципы Draggable, но использует функциональность Sortable.

import { Directive, ElementRef, Input, OnInit, OnDestroy } from '@angular/core';
import { Sortable } from '@shopify/draggable';

@Directive({
  selector: '[appSortable]'
})
export class SortableDirective implements OnInit, OnDestroy {
  @Input() sortableOptions: any;
  private sortableInstance: Sortable;

  constructor(private el: ElementRef) {}

  ngOnInit(): void {
    this.sortableInstance = new Sortable(this.el.nativeElement, this.sortableOptions || {});
    this.bindEvents();
  }

  private bindEvents(): void {
    this.sortableInstance.on('sortable:sorted', (event) => console.log('Sorted', event));
    this.sortableInstance.on('sortable:start', (event) => console.log('Sort started', event));
    this.sortableInstance.on('sortable:stop', (event) => console.log('Sort stopped', event));
  }

  ngOnDestroy(): void {
    if (this.sortableInstance) {
      this.sortableInstance.destroy();
    }
  }
}

Особенности Sortable:

  • Позволяет динамически изменять порядок элементов списка.
  • Поддерживает такие события как sortable:sorted для отслеживания завершения сортировки.
  • Может использоваться совместно с DraggableDirective для расширенных сценариев.

Использование директив в шаблоне Angular

<ul appSortable [sortableOptions]="{ draggable: 'li' }">
  <li appDraggable [draggableOptions]="{ handle: '.handle' }">
    <span class="handle">☰</span> Элемент 1
  </li>
  <li appDraggable>
    Элемент 2
  </li>
  <li appDraggable>
    Элемент 3
  </li>
</ul>

Пояснения:

  • [sortableOptions] передает конфигурацию Sortable, включая селектор элементов для перетаскивания.
  • [draggableOptions] задает индивидуальные параметры перетаскивания, например, handle для ограничения области захвата.
  • Возможность комбинировать Draggable и Sortable директивы позволяет реализовать гибкие интерфейсы.

Работа с событиями и кастомная логика

События Draggable и Sortable предоставляют объект события с полезными свойствами:

  • source — исходный элемент.
  • mirror — клонированный элемент, который перемещается.
  • over и dragEvent — для контроля пересечения с другими элементами.

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

import { fromEventPattern } from 'rxjs';

ngOnInit() {
  const dragStart$ = fromEventPattern(
    (handler) => this.draggableInstance.on('drag:start', handler),
    (handler) => this.draggableInstance.off('drag:start', handler)
  );

  dragStart$.subscribe(event => console.log('RxJS Drag Start', event));
}

Настройка ограничений и контейнеров

Draggable поддерживает несколько опций, важных для UX:

  • handle — CSS-селектор для элемента, который будет точкой захвата.
  • draggable — селектор элементов, которые можно перемещать.
  • mirror — опции клона перемещаемого элемента (constrainDimensions, appendTo).
  • containers — массив DOM элементов для ограничения области перетаскивания.
this.draggableInstance = new Draggable(this.el.nativeElement, {
  draggable: 'li',
  handle: '.handle',
  mirror: {
    constrainDimensions: true
  },
  containers: [document.querySelector('.list-container')]
});

Продвинутые техники интеграции с Angular

  1. Dynamic content: Если элементы добавляются динамически, использовать MutationObserver для переинициализации Draggable или Sortable.

  2. Change detection: Чтобы изменения позиции элементов отражались в Angular, можно вручную обновлять модели через события drag:stop или sortable:sorted.

this.sortableInstance.on('sortable:sorted', (event) => {
  const newIndex = event.newIndex;
  const oldIndex = event.oldIndex;
  this.items.splice(newIndex, 0, this.items.splice(oldIndex, 1)[0]);
});
  1. Lazy initialization: Директива может ждать завершения загрузки данных и только после этого создавать экземпляр Draggable.

Эти подходы обеспечивают гибкую и масштабируемую интеграцию Shopify Draggable в Angular-приложения с использованием директив, сохраняя архитектуру компонентов чистой и модульной.