【问题标题】:Angular 2/4 Observable - why is ng2-dragula sending multiple drop events after a single drag and drop in the UI?Angular 2/4 Observable - 为什么 ng2-dragula 在 UI 中单次拖放后发送多个拖放事件?
【发布时间】:2018-04-02 15:31:34
【问题描述】:

修改问题标题

原来的问题标题是这样的:

Angular 2/4 Observable - 如何修改发出值的对象 在订阅内部而不触发更多事件?

经过不是问题原因的广泛调查,但是这里的 cmets 中有关于如何解决该特定问题的建议。实际问题与同时拥有多个 ng2-dragula 订阅者有关,因此我更新了问题标题以反映这一点。


我正在使用 ng2-dragula 插件并订阅 dropModel 事件。

我面临的问题是在订阅代码中我需要通过重新排列模型中的其他项目来修改模型。这会导致 dropModel 再次触发 dropModel 事件 - 它显然认为是因为我更改了用户执行拖放操作的列表中的模型位置,而不是我的代码。

我尝试了 take(1) 但这并没有解决问题 - 它只是不断地接受一个事件,所以当我在 subscribe 中更改模型时,显然它会再次接受下一个 (1)。

这是代码:

this._dragulaService.dropModel.take(1).subscribe(() => {
  // For ease, we just copy all the values into the model and then destroy
  // the itemsControl, then patch all model values back to the form
  // This is easier than working out which item was dragged and dropped where
  // (and on each drop we want to save the model, so we would need to update it anyway)
  this.itemsControl['controls'].forEach((formGroup, index) => {
    this.template.template_items[index].sort = index; // ensures the API will always return items in the index order
    console.log('copying ID', formGroup['controls'].id.value);
    this.template.template_items[index].id = formGroup['controls'].id.value;
    this.template.template_items[index].item_type = formGroup['controls'].item_type.value;
    this.template.template_items[index].content = formGroup['controls'].content.value;
    this.template.template_items[index].is_completed = formGroup['controls'].is_completed.value;
  });
}

理想情况下,我希望“抓取”第一个 drop 事件(用户发起),然后在订阅代码中,取消订阅或停止接收更多事件,然后处理模型,最后重新订阅。

我知道这有点奇怪 - 在订阅异步代码中,我需要以某种方式“暂停”订阅。尽管“暂停”并不完全正确——我实际上想以某种方式阻止触发新事件,直到我完成对当前事件的处理。暂停只会导致我处理自己的事件(如果有意义的话)。这可能吗?

注意

dragula 在这里绑定的模型是 itemsControls 的动态数组,而不是通常意义上的纯数据模型。因此,我从表单控件中提取数据并插入到实际的数据模型中。

更新 1

我决定记录 Dragula 对我绑定的 itemsControl(AbstractControls 数组)所做的事情。

在拖动之前,我记录了数组中的实际内容:

itemsControl is now this: (4) [FormGroup, FormGroup, FormGroup, FormGroup]

在 dropModel 订阅处理程序中,我记录了一个“dropped”事件和数组的长度。这是我拖放任何项目时的输出,总是相同的输出:

dropped
length is 3
dropped
length is 3
dropped
length is 4
dropped
length is 5
dropped
length is 5
dropped
length is 4
dropped
length is 4

但是,如果我删除我在上面发布的代码(即,我不会触及底层数据模型),这就是输出:

dropped
length is 4
dropped
length is 4

因此,至少这证明了通过重新排序数据,我不仅会引发更多事件(正如我所怀疑的那样),而且还会产生奇怪的副作用,即控件数组的长度正在增加和减少(不知道为什么就是)。

鉴于此输出,我需要一种仅对发出的最后一个事件采取行动的方法。

有没有办法只从 Observable 中获取最后一个事件?

更新 2

根据this,这里真正的问题是ng2-dragula 不支持将dropModel 绑定到FormArray。但似乎有一种解决方法......仍在寻找!

【问题讨论】:

  • 用 throttleTime(0) 或 debounceTime(0) 限制 observable 不会修复它,对吗?
  • @estus 我不知道,但我会尝试。
  • @estus 看来 debounceTime(0) 确实可以解决它...您是否能够创建一个答案来解释为什么会这样?
  • @estus 好的,我的错,在我重新加载应用程序并进一步测试后,它没有修复它
  • 我无法接受这个例子,但过滤和限制是避免递归事件的常用方法。由于这是一个复杂的问题,我建议提供stackoverflow.com/help/mcve 以重现该问题。您也可以尝试更大的延迟,以防万一这可以作为快速修复,throttleTime(10) 或 debounceTime(10)。

标签: angular rxjs observable ng2-dragula


【解决方案1】:

编辑 2022

这个答案现在已经过时了

Dragula 曾经使用 EventEmitters,现在它使用 Observables。请参阅最新的文档。


所以,毕竟,订阅 ng2-dragula 似乎有正确的方法和错误的方法:

错误的方式,这就是我所拥有的:

dragulaService.dropModel.subscribe((result) => { ... }

正确的方法:

private destroy$ = new Subject();

constructor (private _dragulaService: DragulaService) {
  this._dragulaService.dropModel.asObservable()
    .takeUntil(this.destroy$).subscribe((result) => { ... }
}

ngOnDestroy() {
  this.destroy$.next();
}

取自here

现在我在每次放置时只得到一个事件,并且对 AbstractControls 进行排序不会触发进一步的放置事件。

更新

感谢@RichardMatsen 的 cmets,我进一步调查了上述代码解决问题的原因。它没有使用 asObservable() ,所以我不确定为什么在我上面提供的问题 714 的链接中建议这样做。可能需要 asObservable() 以便我们可以正确取消订阅(假设 dragulaService 是一个 EventEmitter)?

之前我用的是我读到的in the docs

dragulaService.destroy(名称)

销毁一个名为 name 的 drake 实例。

摧毁“德雷克”:

  ngOnDestroy(){
    this._dragulaService.destroy('bag-container');
  }  

这并没有取消对 dragulaService 的订阅。因此,当我导航回组件时,它仍在发出事件,这就是我得到多次丢弃的原因。

事实证明订阅和销毁不是推荐的方式来使用dragulaService,所以我已经提交this PR 来更新ng2-dragula 的自述文件,以便将来清楚用户。

【讨论】:

  • 解决问题的可能是.asObservable(),因为订阅保护直到组件被销毁才会激活。
  • 是的,看起来是这样。如果 dropModel 提供了我需要的东西,那么这些都不是必需的 - 有趣的问题记录在这里:github.com/valor-software/ng2-dragula/issues/306
  • 看起来与我们之前的讨论类似。另一方面,我想知道您的asObservable 修复是否有一个原则,因为dropModel 发出一个Angular EventEmitter - 也许将 asObservable 附加到这些总是一个好习惯。
  • 来自 Angular 源代码,export class EventEmitter<T> extends Subject<T>。关于 EventEmitter 不是 observable 的 cmets 是在 Angular 2 Beta 6 的博客文章的 cmets 部分中制作的,然后以粗体字重新发布在 SO 答案中。
  • LOL - 我刚刚意识到,EventEmitter 有一个订阅方法!当他们把它拿走时,我们就会知道我们不能再订阅它了。
【解决方案2】:

如果你总是得到两个发射,一个便宜的答案可能是使用一个标志来区分第一个和第二个

const haveModified = false;
this._dragulaService.dropModel.subscribe(() => {
  if (!haveModified) {
    this.itemsControl['controls'].forEach((formGroup, index) => {
      ...
    });
    haveModified = true;
  } else {
    haveModified = false;
  }
  });
}

更多的 Rx 方法 - 检查下标值,如果两个发射(或属性相同)相同,请使用 distinctUntilChanged(compare: function)

更新

刚刚玩了Plunker,对类中的数据数组进行了简单的排序,但我没有从 Dragula 获得第二次发射。

  constructor(dragulaService: DragulaService) {
    dragulaService.dropModel.subscribe(value => {
      console.log(value)
      console.log('target before sort', this.target)
      this.target.sort();
      console.log('target after sort', this.target)
    })
  }

我无法完全理解您正在执行的操作,但我看到您正在使用对 HTML 控件的引用。你可以通过操作基础数据来做到这一点吗?

更新 #2

一直在为distinctUntilChanged(comparer) 使用比较器,其中涉及比较项目的索引,这应该使该解决方案可行。

但是,由于源代码中的一些 hack(dragula.provider.ts,第 97 行),无法获得索引

target.removeChild(dropElm); // element must be removed for ngFor to apply correctly

dropModel 事件是

this.dropModel.emit([name, dropElm, target, source]);

但是 dropElm 既不在目标也不在源中,所以我们不知道索引是什么。如果像这样发出dropIndex 会更好

this.dropModel.emit([name, dropElm, target, source, dropIndex]);

然后我们可以捕获重复事件。

【讨论】:

  • 表单是从 API 发送的数据数组动态构建的。要获得一个想法,请参阅我对这个问题的回答:stackoverflow.com/questions/42968619/… 注意 patch 和 patchValues 方法 - 并查看 HTML 模板。您将看到绑定不是数据模型,而是控件数组: *ngFor="let child of form.controls.emailsArray.controls;"
  • 这意味着 dragula dropModel 正在拖动一个控件,因此当 dropModel 事件触发时,控件被重新排列并且我收到的值是袋子容器的整个 div(带有 3里面的div,每个都有多个孩子等)。因此无法轻松比较发出的值,因为它是整个 DOM 结构(而不​​是您通常会得到的,即简单的模型值)。
  • 干杯,我会消化的。您能否告诉我订阅中发生的事情的本质(对不起,如果它已经在某处解释过)。
  • 只有一个包,dropModel 是一个 AbstractControls 数组。因此,当用户拖动控件并将其放下时,dragula 对 AbstractControls 进行了重新排序。所以在订阅中我需要遍历这个新订单,然后更新数据的真实模型以反映这个新订单。那么问题是更新数据似乎让dragula认为基础数据已经改变并且它触发另一个drop事件(有时更多)
  • 我很快得出结论,用一组 AbstractControls 构建一个反应式表单并在它们上使用 Dragula 永远不会奏效。
【解决方案3】:

2021 年 Angular 解决方案

错路:this.dragulaService.dropModel.subscribe((result) => { ... }

正道:this.dragulaService.dropModel('GroupName').pipe(takeUntil(this.destroyDragulaEventHandler$)).subscribe((result) => { ... }

使用 rxjs 主题创建实例变量: private destroyDragulaEventHandler$ = new Subject();

确保在 ngOnDestory 中进行一些清理:ngOnDestroy(){ this.destroyDragulaEventHandler$.next(); this.dragulaService.destroy("groupName"); }

【讨论】:

    猜你喜欢
    • 2018-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-29
    • 1970-01-01
    • 2020-05-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多