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