Angular 2 拖放指令极慢

Angular 2 drag and drop directive extremely slow

我正在尝试实现自定义拖放指令。它有效,但速度非常慢,我认为这种缓慢可以追溯到 Angular 2,因为我以前从未遇到过这种缓慢。只有当我将事件侦听器附加到 dragoverdrag 事件(即频繁发送的事件)时才会出现缓慢,即使我只做 return false在他们里面。

这是我的指令代码:

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

这里是相关样式sheet摘录:

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

如您所见,我所做的只是用黄色突出显示被拖过的元素。当我不处理 dragover 事件时它工作得很快,但是我 必须 处理它以支持删除。当我处理 dragover 事件时,一切都变慢到无法忍受的程度!!

编辑 我正在使用 angular beta 2.0.0-beta.8

编辑 #2 我尝试使用 chrome 的分析器分析代码,结果如下:

看标记线,奇怪的可疑...

EDIT #3 发现问题:确实是由于 Angular 2 的变化检测。在我的案例中,拖放操作是在一个包含大量绑定和指令的非常密集的页面上完成的。当我注释掉给定列表以外的所有内容时,它再次快速运行......现在我需要你的帮助来找到解决这个问题的方法!

回答我自己的问题(问题已解决)。

缓慢问题是由于我的标记中的数据绑定效率低下,导致 Angular 浪费大量时间在我的视图模型上调用函数。我有很多这样的绑定:

*ngFor="#a of someFunc()"

这导致 Angular 不确定数据是否已更改,函数 someFunconDragOver 的每个 运行 之后被一次又一次地调用(这大约是每 350 毫秒一次),即使在拖放过程中数据没有改变。我更改了这些绑定以引用我的 class 中的简单属性,并将填充它们的代码移到了它应该在的位置。一切又开始飞速发展了!

刚遇到同样的问题。即使使用高效的 ngFor 代码,如果您有大量可拖动项目,拖放仍然会非常慢。

我的诀窍是用 ngZone 使 Angular 之外的所有拖放事件侦听器 运行,然后使 运行 回到 [=25] =] 掉落时。这使得 Angular 避免检查您移动可拖动项目的每个像素的检测。

注入:

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

正在初始化:

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

掉落时:

el.addEventListener('drop', (e) => {
    this.ngZone.run(() => {
        console.log("dropped");
    })
})

我遇到了类似的问题,当我在 *ngFor 中放置多个拖动区域时,我的拖放也变得非常慢。

我通过将更改检测策略更改为子组件的 OnPush 解决了这个问题。

然后在每次拖动项目时,执行 markForCheck()

constructor(private changeDetectorRef: ChangeDetectorRef) {}
  
// Callback function
public onDrag() {
  this.changeDetectorRef.markForCheck();
}

对我来说,问题是即使在生产中也打开了开发模式。当我用 ng build --evn-prod 编译它时,拖放突然变得非常快。

感谢大家的讨论。 最终得到一个非常有效的简单解决方案:

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
}

我最近遇到了类似的问题。它在 Angular 6 环境中使用反应形式。这就是我针对我的情况解决的方法:

基本上简单地说,我在拖动发生时关闭了该组件的变化检测。

  1. 导入 ChangeDetectorRef:
    import { ChangeDetectorRef } from '@angular/core';
  1. 将其注入到构造函数中:
    constructor(private chngDetRef: ChangeDetectorRef) { //...
  1. 在 dragStart 上分离它:
    private onDragStart(event, dragSource, dragIndex) {
        // ...
        this.chngDetRef.detach();
        // ...
  1. 在拖放和拖动结束时重新附加它:
    private onDrop(event, dragSource, dragIndex) {
        // ...
        this.chngDetRef.reattach();
        // ...

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

如果您有很多父组件或分层组件,您可能还需要对它们的变化检测做一些事情才能看到实质性的改进。

我在 angular 项目中遇到了拖放问题 - detectChanges(reattach(), deTached ..), outSide Angular (ngZone) 无法解决这个问题。 现在我通过使用 jquery 解决了这个问题,我在构造函数中为我的 div 内容绑定了事件。

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

       console.log('here your logic')

    });
}

通过这种方式,您也可以实现其他事件(dragleave、drop、'dragover')。它对我来说非常好用而且速度很快。

这是对旧 post 的跟进,但拖放“仍然是一个问题。我的特殊问题涉及一个页面,上面有超过 130 个组件,拖放非常糟糕。我试过了在此和其他 post 中提供的各种建议,只有极小的改进。

最后,我决定不使用 ngZone 解决方案,而是尝试将 (dragOver)="function()" 更改为原生 ondragover="event.preventDefault()"。我让所有其他事件处理程序(即 dragStartdragEnterdragLeavedragDropdragEnd)根据需要通过 Angular。我的拖放响应从几秒缩短到几毫秒。

如果有人能提供替代的 dragOver 绕过更改检测的事件处理程序,那就太好了。