Drag ‘n Drop in Angular: Part 1 – The Draggable Directive

The first part of this series starts with the foundation: the draggable directive. This directive has only one responsibility: tracking the dragStart, dragMove and dragEnd events.

Background

To have the ultimate freedom, I recommend to stay away from the HTML5 Drag and Drop API. Of course, HTML5 drag ‘n drop can be extremely useful, especially for file uploads with dropzones. But it also has limitations. For example, it can be hard to disable the so-called “ghost element”, when you want to use your own helper element.

Not to mention the dragoverevent, which is fired every few hundred milliseconds?

For me, HTML5 pointer events were good enough. For some browsers you will need the polyfill, but you can easily install it and activate it in yourpolyfills.ts of your Angular app.

Summary

This is how I want to use the draggable directive:

<div class="box" appDraggable>
  My box!
</div>

It should also have 3 output event emitters for the dragStartdragMove and dragEndevents:

<div class="box" 
     appDraggable
     (dragStart)="onDragStart($event)"
     (dragMove)="onDragMove($event)"
     (dragEnd)="onDragEnd($event)">
  My box!
</div>

A simple version of the directive looks like this:

@Directive({
  selector: '[appDraggable]'
})
export class DraggableDirective {
  @Output() dragStart = new EventEmitter<PointerEvent>();
  @Output() dragMove = new EventEmitter<PointerEvent>();
  @Output() dragEnd = new EventEmitter<PointerEvent>();

  private dragging = false;

  @HostListener('pointerdown', ['$event'])
  onPointerDown(event: PointerEvent): void {
    this.dragging = true;
    this.dragStart.emit(event);
  }

  @HostListener('document:pointermove', ['$event'])
  onPointerMove(event: PointerEvent): void {
    if (!this.dragging) {
      return;
    }

    this.dragMove.emit(event);
  }

  @HostListener('document:pointerup', ['$event'])
  onPointerUp(event: PointerEvent): void {
    if (!this.dragging) {
      return;
    }

    this.dragging = false;
    this.dragEnd.emit(event);
  }
}

I also demonstrate a more reactive approach using RxJS. Please watch my YouTube video to see both implementations.

Leave a Reply

Your email address will not be published. Required fields are marked *