11

I would like to implement a drag and drop using Angular 2. I have some items:

<div class="item"></div>

which I would like to be able to drag and drop in a container:

<div class="container"></div>

I can not find any good source of information for doing this in Angular 2. I found this file: https://github.com/AngularClass/angular2-examples/blob/master/rx-draggable/directives/draggable.ts which I tried but I could not get it to work, I am also not entirely sure on how it should work.

How do I implement it?

rablentain
  • 6,641
  • 13
  • 50
  • 91
  • I realized that I forgot to add the directive to the component directives list. Although, now it seems that the .toRx method on the EventEmitter is not available anymore. How should that be changed? – rablentain Feb 27 '16 at 09:19
  • As of Beta 1, [you no longer need to call .toRx() on the emitter](http://stackoverflow.com/questions/33530726/angular-2-eventemitter-broadcasting-next-from-a-service-function/33534404#33534404). – Lucio Mollinedo Jun 07 '16 at 22:30
  • it's pretty good described here http://stackoverflow.com/a/38710223/2173016 – Alex Filatov Aug 02 '16 at 01:21

8 Answers8

22

try this:

function onDragStart(event, data) {
  event.dataTransfer.setData('data', data);
}
function onDrop(event, data) {
  let dataTransfer = event.dataTransfer.getData('data');
  event.preventDefault();
}
function allowDrop(event) {
  event.preventDefault();
}
<div (drop)="onDrop($event, dropData)" (dragover)="allowDrop($event)"></div>
<div draggable="true" (dragstart)="onDragStart($event, dragData)"></div>
SnailCoil
  • 818
  • 1
  • 9
  • 23
Ruslan
  • 229
  • 2
  • 3
  • What is `dropData` for/What is it? – Koopakiller Nov 09 '18 at 20:44
  • Not that easy as it looks. When there are are multiple drop zones & multiple cards to be dragged/dropped, due to angular change detection, it is really slow when we use html5 drag/drop feature. – JPS Dec 18 '18 at 04:39
8

Try this:

systemjs.config:

var map =       {
    'app': './wwwroot/ngApp',
    'rxjs': './wwwroot/js/libs/rxjs',
    '@angular': './wwwroot/js/libs/@angular',
    'dragula': './wwwroot/js/libs/dragula/dist/dragula.js',
    'ng2-dragula': './wwwroot/js/libs/ng2-dragula'
  };

var packages = {
    'app': { main: 'main.js', defaultExtension: 'js' },
    'rxjs': { defaultExtension: 'js' },
    'dragula': { defaultExtension: 'js' },
    'ng2-dragula': {defaultExtension: 'js' }
  };

var config = {
    map: map,
    packages: packages  
  }`

Then import

import {Dragula, DragulaService} from 'ng2-dragula/ng2-dragula';

And in @Component

directives: [Dragula], viewProviders: [DragulaService]
Satch3000
  • 47,356
  • 86
  • 216
  • 346
  • 3
    That's a very nice implementation of drag and drop. I really like your business logic there. Not enough jquery though – Ced Sep 24 '16 at 17:50
  • unable to find this file 'systemjs.config', where it is located? – pjay Jan 10 '18 at 07:02
  • @pjay probably on Angular CLI, just run 'npm install --save ng2-dragula' instead of the first step. – Scuba Kay Apr 20 '18 at 14:10
6

I would recommend using Ng2-Dragula.

it is the angular2 dependency which provides drag n drop functionality to your application easily.

All you need to do is to install this dependency through npm.

npm install ng2-dragula dragula --save

add includes inside index.html and configure system as

<script src="/node_modules/ng2-dragula/bundles/ng2-dragula.js"></script>
<link href="/node_modules/ng2-dragula/src/public/css/dragula.min.css" rel='stylesheet' type='text/css'>
<script>
    System.config({        
    paths:{
        'dragula'         : '../node_modules/dragula/dist/dragula.min.js'
    },
    packages: {            
      app: {
        format: 'register',
        defaultExtension: 'js'
      }
    }
  });

 System.import('app/main')
       .then(null, console.error.bind(console));
</script>

import it inside the component where you want to use drag n drop and you are good to go.

@Component({
  selector: 'sample',
  directives: [Dragula],
  viewProviders: [DragulaService],
  template:`
  <div>
    <div class='wrapper'>
      <div class='container' [dragula]='"first-bag"'>
        <div>You can move these elements between these two containers</div>
        <div>Moving them anywhere else isn't quite possible</div>
        <div>There's also the possibility of moving elements around in the same container, changing their position</div>
      </div>
      <div class='container' [dragula]='"first-bag"'>
        <div>This is the default use case. You only need to specify the containers you want to use</div>
        <div>More interactive use cases lie ahead</div>
        <div>Make sure to check out the <a href='https://github.com/bevacqua/dragula#readme'>documentation on GitHub!</a></div>
      </div>
    </div>
  </div>
  `
})
class Sample {}
Bhushan Gadekar
  • 13,485
  • 21
  • 82
  • 131
4

I also started out with the same example for my draggables - and did get it to work. The example is from an early version of angular2, so some changes are necessary. Check out this answer. It has some of those changes in it. Best of luck!

My own slightly more general purpose version goes like this:

import {Directive, EventEmitter, HostListener, Output} from 'angular2/core';
import {Observable} from 'rxjs/Observable';

@Directive({
  selector: '[draggable]'
})
export class DraggableDirective {

  @Output() mousedrag: Observable<{x: number, y: number}>;
  @Output() dragend = new EventEmitter<void>();
  mousedown = new EventEmitter<MouseEvent>();
  mousemove = new EventEmitter<MouseEvent>();
  dragActive = false;

  @HostListener('document:mouseup', ['$event'])
  onMouseup(event) {
    if(this.dragActive) {
      this.dragend.emit(null);
      this.dragActive = false;
    }
  }

  @HostListener('mousedown', ['$event'])
  onMousedown(event: MouseEvent) {
    this.mousedown.emit(event);
  }

  @HostListener('document:mousemove', ['$event'])
  onMousemove(event: MouseEvent) {
    if(this.dragActive) {
      this.mousemove.emit(event);
      return false;
    }
  }

  constructor() {
    this.mousedrag = this.mousedown.map((event) => {
      this.dragActive = true;
      return { x: event.clientX, y: event.clientY };
    }).flatMap(mouseDownPos => this.mousemove.map(pos => {
      return { x: pos.clientX - mouseDownPos.x, y: pos.clientY - mouseDownPos.y };
    }).takeUntil(this.dragend));
  }
}

Take that with a pinch of salt as I am currently chasing a memory leak which seems to be related to this directive. I will update if I find an issue.

Community
  • 1
  • 1
LOAS
  • 7,161
  • 2
  • 28
  • 25
3

I have done it using jquery draggable - integrated in Angular

import {Component, ElementRef, OnInit} from '@angular/core';'

declare var jQuery:any;

@Component({
    selector: 'jquery-integration',
    templateUrl: './components/jquery-integration/jquery-integration.html'
})
export class JqueryIntegration implements OnInit {
    elementRef: ElementRef;
    constructor(elementRef: ElementRef) {
        this.elementRef = elementRef;
    }
    ngOnInit() {
        jQuery(this.elementRef.nativeElement).draggable({containment:'#draggable-parent'});
    }
}

More info here: http://www.syntaxsuccess.com/viewarticle/using-jquery-with-angular-2.0

Live demo: http://www.syntaxsuccess.com/angular-2-samples/#/demo/jquery

TGH
  • 38,769
  • 12
  • 102
  • 135
  • 4
    I would rather do it in Angular2 without the use of jQuery. As it is said in the article you linked, using jQuery is not the Angular way of doing things. But I will have this solution in mind if I do not find anything better. – rablentain Feb 27 '16 at 09:16
0

Russian's answer works well but change detection makes it slow. You can fix this by using a custom directive.

Credit for this comes from here https://netbasal.com/angular-2-escape-from-change-detection-317b3b44906b

import {Directive, ElementRef, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output} from '@angular/core';

@Directive({
  selector: '[appIgnoreEvent]'
})
export class IgnoreEventDirective implements OnInit, OnDestroy {
  @Input() event = 'dragover';
  @Output() emitter = new EventEmitter();
  private _handler: Function;
  constructor(private _ngZone: NgZone, private el: ElementRef) {}

  ngOnInit() {
    this._ngZone.runOutsideAngular(() => {
      const nativeElement = this.el.nativeElement;
      this._handler = $event => {
        this.emitter.emit($event);
      };

      nativeElement.addEventListener(this.event, this._handler, false);
    });
  }

  ngOnDestroy() {
    this.el.nativeElement.removeEventListener(this.event, this._handler);
  }
}

Then pass the dragOver event to your emitter instead.

<div (drop)="onDrop($event, dropData)" appIgnoreEvent (emitter)="allowDrop($event)"></div>

Not enough reputation for me to add this as a comment

0

i made this component for one of my projects hope this will help.

import { Component, OnInit, ViewChild, ElementRef, HostListener } from '@angular/core';

@Component({
    selector: 'app-video-call-container',
    templateUrl: './video-call-container.component.html',
    styleUrls: ['./video-call-container.component.css']
})
export class VideoCallContainerComponent implements OnInit {

    constructor() { }

    mouseCursorX = 0;
    mouseCursorY = 0;
    dragActive = false;



    @ViewChild('container') container: ElementRef;

    @HostListener('window:mouseup', ['$event'])
    mouseUp(event) {
        if (this.dragActive == true) {
            this.dragActive = false;
        }
    }

    @HostListener('window:mousemove', ['$event'])
    mouseMove(event) {
        if (this.dragActive) {
            var left = this.mouseCursorX - event.clientX;
            var top = this.mouseCursorY - event.clientY;
            var offsets = this.getElementOffsets(this.container.nativeElement);
            var posLeft = (offsets.left - left);
            var posTop = (offsets.top - top);
            if (posLeft > 0 && posLeft <= window.innerWidth - this.container.nativeElement.offsetWidth && posTop > 0 && posTop <= window.innerHeight - this.container.nativeElement.offsetHeight) {
                this.container.nativeElement.style.left = posLeft + "px";
                this.container.nativeElement.style.top = posTop + "px";
            }
            this.mouseCursorX = event.clientX;
            this.mouseCursorY = event.clientY;
        }
    }

    drag(event) {
        this.dragActive = true;
        this.mouseCursorX = event.clientX;
        this.mouseCursorY = event.clientY;
    }


    getElementOffsets(elem) {
        return {
            top: this.getOffsetTop(elem),
            left: this.getOffsetLeft(elem)
        }
    }

    getOffsetTop(elem) {
        var offsetTop = 0;
        do {
            if (!isNaN(elem.offsetTop)) {
                offsetTop += elem.offsetTop;
            }
        } while (elem = elem.offsetParent);
        return offsetTop;
    }

    getOffsetLeft(elem) {
        var offsetLeft = 0;
        do {
            if (!isNaN(elem.offsetLeft)) {
                offsetLeft += elem.offsetLeft;
            }
        } while (elem = elem.offsetParent);
        return offsetLeft;
    }


    ngOnInit(): void {
    }

}
<div class="container-box" #container>
    <div class="container-header" (mousedown)="drag($event)">
        <label>Vikas Kandari</label>
        <span>Ringing...</span>
        <button><i class="fa fa-close"></i></button>
    </div>
    <div class="container-body">
        <div class="video-call-caller">
            <video></video>
        </div>
        <div class="video-call-receiver">
            <video></video>
        </div>
    </div>
</div>
.container-box {
    position: fixed;
    background-color: #fefefe;
    border-radius: 2px;
    box-shadow: 0 0 0 1px rgba(0,0,0,.15), 0 2px 3px rgba(0,0,0,.2);
    z-index: 9999999999999;
    right: 15px;
    bottom: 50px;
    width: 300px;
    height: 400px;
}

.container-header {
    width: 100%;
    float: left;
    padding: 10px;
    border-bottom: 1px solid #ddd;
    cursor: move;
}

.container-header>label {
    display: block;
    width: 100%;
    float: left;
    margin: 0px;
    cursor: move;
}

.container-header>button {
    position: absolute;
    right: 10px;
    top: 10px;
    border-radius: 100%;
    height: 30px;
    width: 30px;
    border: none;
    font-size: 20px;
    background: transparent;
    cursor: pointer;
}

.container-header>span {
    display: block;
    float: left;
    width: 100%;
    cursor: move;
}

.container-header:hover>button {
    background: #e6ecf0;
}
Vikas Kandari
  • 1,612
  • 18
  • 23
0

Super easy solution which ChatGPT gave to me and i tested it (it uses tailwindcss classes):

To implement dragging, you can use the "Draggable" directive from the "@angular/cdk/drag-drop" package.

  1. Install the package: npm install @angular/cdk

  2. Import the Draggable directive and DragDropModule into the module where you will use the markup (app.module.ts in base case):

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

@NgModule({
  imports: [
    ...
    DragDropModule
  ],
  ...
})
export class YourModule { }
  1. Add the Draggable directive to your div:
<div id="parent" class="border h-32 w-56 relative">
  <div cdkDrag class="border absolute top-0 left-0 w-24 h-16 cursor-move"></div>
</div>

With this directive, you can drag elements within their parent container.

If you want the div to move only horizontally or vertically, you can add the parameter cdkDragFreeDrag="false".

Another useful parameter is cdkDragBoundary, which allows you to define the rectangle in which the element can be moved.

<div cdkDrag class="border absolute top-0 left-0 w-24 h-16 cursor-move"
  [cdkDragBoundary]="boundary"></div>

And in the component, define the boundary property:

import { Component } from '@angular/core';

@Component({
   ...
})
export class YourComponent {
   boundary = '#parent'; // a selector of the parent container
}

Now the div will move only within its parent container, and its movement will be limited by its boundaries.