Angular 2 drag and drop directive extremely slow
Asked Answered
S

8

12

I am trying to implement a custom drag and drop directive. It works, but it is extremely slow, and I think the slowness can be tracked to Angular 2 because I've never encountered this slowness before. The slowness only occurs when I attach an event listener to the dragover or drag events (i.e. the events which are sent frequently), even if I do nothing but return false in them.

Here's my directive code:

import {Directive, ElementRef, Inject, Injectable} from 'angular2/core';

declare var jQuery: any;
declare var document: any;

@Directive({
    selector: '.my-log',
    host: {
        '(dragstart)': 'onDragStart($event)',
        '(dragover)': 'onDragOver($event)',
        '(dragleave)': 'onDragLeave($event)',
        '(dragenter)': 'onDragEnter($event)',
        '(drop)': 'onDrop($event)',
    }
})
@Injectable()
export class DraggableDirective {
    refcount = 0;
    jel;

    constructor( @Inject(ElementRef) private el: ElementRef) {
        el.nativeElement.setAttribute('draggable', 'true');
        this.jel = jQuery(el.nativeElement);
    }

    onDragStart(ev) {
        ev.dataTransfer.setData('Text', ev.target.id);
    }

    onDragOver(ev) {
        return false;
    }

    onDragEnter(ev) {
        if (this.refcount === 0) {
            this.jel.addClass('my-dragging-over');
        }
        this.refcount++;
    }

    onDragLeave(ev) {
        this.refcount--;
        if (this.refcount === 0) {
            this.jel.removeClass('my-dragging-over');
        }
    }

    onDrop(ev) {
        this.jel.removeClass('my-dragging-over');
        this.refcount = 0;
    }
}

Here's the relevant style sheet excerpt:

.my-log.my-dragging-over {
    background-color: yellow;
}

As you can see all I'm doing is highlighting the element being dragged over in yellow. And it works fast when I don't handle the dragover event, however I must handle it to support dropping. When I do handle the dragover event, everything slows down to unbearable levels!!

EDIT I am using angular beta 2.0.0-beta.8

EDIT #2 I tried profiling the code using chrome's profiler, these are the results:

Imgur

Look at the marked line, it is strangely suspicious...

EDIT #3 Found the problem: it was indeed due to Angular 2's change detection. The drag and drop operation in my case is done on a very dense page with a lot of bindings and directives. When I commented out everything except the given list, it worked fast again... Now I need your help in finding a solution to this!

Siblee answered 2/3, 2016 at 19:37 Comment(7)
Can you explain in simple terms what the cause of the problem is? I can't understand much from reading the issue...Siblee
Most likely because the mix of ng2 and jQuery, have you tried relying only in ng2 + RxJS? plnkr.co/edit/LD5FJaI4OOFbKfvhjD4e?p=previewSeaver
This is not the cause, sorry, I tried removing all traces of jQuery. Same result.Siblee
Then you'll have to provide a reproduction. I cannot see the same behavior in my plnkr.Seaver
@EricMartinez here's a plnkr plnkr.co/edit/cY1Adg5M8Ox0Ss4a6jSn?p=preview strangely enough it doesn't happen there!!! would you be willing to look at my original site and see what the difference might be?Siblee
@EricMartinez it was indeed because of angular 2's change detection. Look at edit #3 in my question above...Siblee
Thank you for this post! I had suspected that the issue was my function calls in the view, but was reluctant to spend the time to refactor before finding this post, as I wasn't sure. After reading, I spent the hour to go refactor and, yes, HUGE improvement. Thanks again, @AviadP.Sachsse
S
4

Answering my own question (problem was solved).

The slowness problem was due to inefficient data bindings in my markup, which caused Angular to waste a lot of time calling functions on my view model. I had many bindings of this sort:

*ngFor="#a of someFunc()"

This caused Angular to be unsure whether data has changed or not, and the function someFunc was getting called again and again after every run of onDragOver (which is a about once every 350ms) even though data was not changing during the drag and drop process. I changed these bindings to refer to simple properties in my class, and moved the code that populates them where it was supposed to be. Everything started moving lightning fast again!

Siblee answered 2/3, 2016 at 23:15 Comment(3)
Can you tell me what someFunc() has ?Hemitrope
Can you tell me what someFunc() has ?Hemitrope
It created the list of elements dynamically, a new copy, in each call.Siblee
A
13

Just went through some trouble with the same problem. Even with efficient ngFor code, drag and drop can still be crazy slow if you have a large number of draggable items.

The trick for me was to make all drag and drop event listeners run outside of Angular with ngZone, then make it run back in Angular when dropped. This makes Angular avoid checking for detection for every pixel you move the draggable item around.

Inject:

import { Directive, ElementRef, NgZone } from '@angular/core';
constructor(private el: ElementRef, private ngZone: NgZone) {}

Initializing:

ngOnInit() {
  this.ngZone.runOutsideAngular(() => {
    el.addEventListener('dragenter', (e) => {
      // do stuff with e or el
    });
...

On drop:

el.addEventListener('drop', (e) => {
    this.ngZone.run(() => {
        console.log("dropped");
    })
})
Amulet answered 9/12, 2016 at 1:18 Comment(1)
Finally a solution! Drag'nDrop was painfully slow on a tree structure with many bindings involved. 10-15sec of spinning 'dragover' events was no exception. Thank you.Mathison
S
8

Thanks to everybody for this discussion. End up with simple solution which works like a charm:

constructor(private cd: ChangeDetectorRef) {
}

drag(event: DragEvent): void {
    this.cd.detach();
    // Begin the job (use event.dataTransfer)
}

allowDrop(event: DragEvent): void {
    event.preventDefault();
}

drop(event: DragEvent): void {
    event.preventDefault();
    this.cd.reattach();
    // Do the job
}
Suppressive answered 20/12, 2018 at 6:53 Comment(1)
This solution works well for me. I only had to move the reattachment of the ChangeDetectorRef to a separate "dragend" event handler. Otherwise no more changes are detected after cancelling an ongoing drag.Topsyturvy
S
4

Answering my own question (problem was solved).

The slowness problem was due to inefficient data bindings in my markup, which caused Angular to waste a lot of time calling functions on my view model. I had many bindings of this sort:

*ngFor="#a of someFunc()"

This caused Angular to be unsure whether data has changed or not, and the function someFunc was getting called again and again after every run of onDragOver (which is a about once every 350ms) even though data was not changing during the drag and drop process. I changed these bindings to refer to simple properties in my class, and moved the code that populates them where it was supposed to be. Everything started moving lightning fast again!

Siblee answered 2/3, 2016 at 23:15 Comment(3)
Can you tell me what someFunc() has ?Hemitrope
Can you tell me what someFunc() has ?Hemitrope
It created the list of elements dynamically, a new copy, in each call.Siblee
E
2

I had a similar issue recently. It was in an Angular 6 environment using reactive forms. This is how I solved it for my situation:

Basically and briefly, I turned off change detection on that component while dragging was taking place.

  1. import ChangeDetectorRef:
    import { ChangeDetectorRef } from '@angular/core';
  1. inject it into the constructor:
    constructor(private chngDetRef: ChangeDetectorRef) { //...
  1. detach it on dragStart:
    private onDragStart(event, dragSource, dragIndex) {
        // ...
        this.chngDetRef.detach();
        // ...
  1. reattach it on drop and dragEnd:
    private onDrop(event, dragSource, dragIndex) {
        // ...
        this.chngDetRef.reattach();
        // ...

    private onDragEnd(event, dragIndex) {
        // ...
        this.chngDetRef.reattach();
        // ...

If you have a lot of parent or layered components, you may have to do something about their change detection as well in order to see a substantial improvement.

Erenow answered 6/2, 2019 at 22:1 Comment(0)
E
2

This is a follow up to an old post, but drag and drop is "still an issue. My particular problem involved a page with over 130 components on it and drag and drop was abysmal. I tried the various suggestions offered in this and other posts with only minimal improvement.

Finally, I decided that rather than the ngZone solution, I would try changing (dragOver)="function()" to the native ondragover="event.preventDefault()". I let all the other event handlers (i.e. dragStart, dragEnter, dragLeave, dragDrop, dragEnd) go through Angular as was needed. My drag and drop response went from seconds to milliseconds.

It would be great anyone could provide an alternative dragOver event handler that bypasses change detection.

Empiric answered 12/4, 2020 at 17:0 Comment(0)
C
0

I had a similar issue, also my drag and drop became very slow when I did put multiple drag zones inside a *ngFor.

I solved this by changing the change detection strategy to OnPush of the child component.

Then on every time when an item get dragged, do markForCheck().

constructor(private changeDetectorRef: ChangeDetectorRef) {}
  
// Callback function
public onDrag() {
  this.changeDetectorRef.markForCheck();
}
Colza answered 16/8, 2017 at 9:13 Comment(0)
F
0

Issue for me was that Development mode was turned on even in production. When i compiled it with ng build --evn-prod drag and drop is suddenly blazing fast.

Fairfax answered 5/6, 2018 at 10:10 Comment(0)
D
0

I had the same problem with drag & drop in my angular project - detectChanges(reattach(), deTached ..), outSide Angular (ngZone) couldn't solve this problem. Now I solved this problem by using jquery , I bond events in constructor for my div content.

constructor() {
    $(document).delegate('#jsDragZone', 'dragenter', function (e) {

       console.log('here your logic')

    });
}

in this way you can implement other events too (dragleave, drop, 'dragover'). It's worked very nice and fast for me.

Digamma answered 3/4, 2020 at 6:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.