Skip to content

Instantly share code, notes, and snippets.

@dirkluijk
Created March 18, 2018 23:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dirkluijk/51005d6c9a57a9b9c6d12209d49e6c1b to your computer and use it in GitHub Desktop.
Save dirkluijk/51005d6c9a57a9b9c6d12209d49e6c1b to your computer and use it in GitHub Desktop.
Drag/drop in Angular - part 1

Drag/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/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. Or what about the dragover event, 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 your polyfills.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 dragStart, dragMove and dragEnd events:

<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 demonstrated a more reactive approach using RxJS. Please watch my YouTube video to see both implementations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment