2

编辑关于问题标题更改

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

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

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


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

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

我尝试了 take(1) 但这并没有解决问题 - 它只是不断地接受一个事件,所以当我在订阅中更改模型时,显然它会再次接受下一个 (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;
  });
}

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

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

笔记

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

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

4

3 回答 3

3

编辑 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();
}

取自这里

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

更新

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

以前我使用了我在文档中阅读的内容:

dragulaService.destroy(名称)

销毁名为 name 的 drake 实例。

摧毁“德雷克”:

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

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

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

于 2017-10-22T21:15:05.997 回答
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),其中涉及比较项目的索引,这应该使该解决方案可行。

但是,由于源代码中的一些黑客行为(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]);

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

于 2017-10-21T21:53:13.730 回答
0

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

于 2021-10-05T20:53:01.373 回答