Reputation: 6715
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
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
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
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
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
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
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
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