rablentain
rablentain

Reputation: 6715

How to implement drag and drop in Angular2?

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?

Upvotes: 11

Views: 42150

Answers (7)

Vikas Kandari
Vikas Kandari

Reputation: 1851

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;
}

Upvotes: 0

Justin M
Justin M

Reputation: 1

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

Upvotes: 0

Ruslan
Ruslan

Reputation: 229

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>

Upvotes: 22

TGH
TGH

Reputation: 39248

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

Upvotes: 3

Satch3000
Satch3000

Reputation: 49384

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]

Upvotes: 8

LOAS
LOAS

Reputation: 7282

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.

Upvotes: 4

Bhushan Gadekar
Bhushan Gadekar

Reputation: 13805

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 {}

Upvotes: 6

Related Questions