Angular 2/4 Observable - 为什么 ng2-dragula 在 UI 中一次拖放后发送多个拖放事件?
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 - how to modify the object that emits values
inside the subscribe without firing more events?
经过广泛调查后发现这不是问题的原因,但此处的评论中有关于如何解决该特定问题的建议。实际问题与同时存在多个 ng2-dragula 订阅者有关,因此我更新了问题标题以反映这一点。
我正在使用 ng2-dragula 插件并订阅 dropModel 事件。
我面临的问题是,在订阅代码中,我需要通过 re-arranging 模型中的其他项目来修改模型。这导致 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;
});
}
理想情况下,我希望 'grab' 第一个删除事件(用户发起),然后在订阅代码中取消订阅或停止接收更多事件,然后处理模型,最后重新订阅。
我知道这有点奇怪 - 在订阅异步代码中,我需要以某种方式 'pause' 订阅。尽管 'pause' 不太正确 - 我实际上想以某种方式阻止触发新事件,直到我完成当前事件的处理。暂停只会导致我处理自己的事件(如果这有意义的话)。这可能吗?
备注
这里dragula绑定的模型是itemsControls的动态数组,不是正常意义上的纯数据模型。因此,为什么我要从表单控件中提取数据并插入到实际数据模型中。
更新 1
我决定记录 dragula 使用我绑定的 itemsControl(抽象控件数组)所做的事情。
在拖动之前,我记录了数组中的实际内容:
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
所以至少这证明了通过 re-sorting 数据我不仅引发了更多事件(正如我所怀疑的那样)而且还引发了奇怪的副作用,即控件数组的长度在增加和减少(不确定为什么会这样)。
鉴于此输出,我需要一种方法来仅对发出的最后一个事件采取行动。
有没有办法只从 Observable 获取最后一个事件?
更新 2
根据 this,这里真正的问题是 ng2-dragula 不支持将 dropModel 绑定到 FormArray。但似乎有解决方法...仍在搜索中!
如果你总是得到两次发射,一个廉价的答案可能是使用标志来区分第一次和第二次
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,对 class 中的数据数组进行简单排序,但我没有从 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]);
然后我们可以捕获重复事件。
编辑 2022
此答案现已过时
Dragula 以前使用 EventEmitter,现在使用 Observable。请查看最新文档。
所以,在这一切之后,订阅 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 的评论,我进一步调查了上述代码解决问题的原因。它没有使用 asObservable() 来执行此操作,因此我不太确定为什么在 link 中建议我在上面提供的问题 714 中这样做。可能需要 asObservable() 以便我们可以正确取消订阅(假设 dragulaService 是一个 EventEmitter)?
以前我用的是我读到的in the docs:
dragulaService.destroy(name)
Destroys a drake instance named name.
摧毁'drake':
ngOnDestroy(){
this._dragulaService.destroy('bag-container');
}
这并没有取消订阅 dragulaService。因此,当我导航回该组件时,它仍在发出事件,这就是我多次掉落的原因。
事实证明,订阅和销毁 不是推荐的方式 使用 dragulaService,所以我已经提交 this PR 更新 ng2 的自述文件- dragula 向未来的用户说明这一点。
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"); }
关于问题标题更改的编辑
原题目是这样的:
Angular 2/4 Observable - how to modify the object that emits values inside the subscribe without firing more events?
经过广泛调查后发现这不是问题的原因,但此处的评论中有关于如何解决该特定问题的建议。实际问题与同时存在多个 ng2-dragula 订阅者有关,因此我更新了问题标题以反映这一点。
我正在使用 ng2-dragula 插件并订阅 dropModel 事件。
我面临的问题是,在订阅代码中,我需要通过 re-arranging 模型中的其他项目来修改模型。这导致 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;
});
}
理想情况下,我希望 'grab' 第一个删除事件(用户发起),然后在订阅代码中取消订阅或停止接收更多事件,然后处理模型,最后重新订阅。
我知道这有点奇怪 - 在订阅异步代码中,我需要以某种方式 'pause' 订阅。尽管 'pause' 不太正确 - 我实际上想以某种方式阻止触发新事件,直到我完成当前事件的处理。暂停只会导致我处理自己的事件(如果这有意义的话)。这可能吗?
备注
这里dragula绑定的模型是itemsControls的动态数组,不是正常意义上的纯数据模型。因此,为什么我要从表单控件中提取数据并插入到实际数据模型中。
更新 1
我决定记录 dragula 使用我绑定的 itemsControl(抽象控件数组)所做的事情。
在拖动之前,我记录了数组中的实际内容:
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
所以至少这证明了通过 re-sorting 数据我不仅引发了更多事件(正如我所怀疑的那样)而且还引发了奇怪的副作用,即控件数组的长度在增加和减少(不确定为什么会这样)。
鉴于此输出,我需要一种方法来仅对发出的最后一个事件采取行动。
有没有办法只从 Observable 获取最后一个事件?
更新 2
根据 this,这里真正的问题是 ng2-dragula 不支持将 dropModel 绑定到 FormArray。但似乎有解决方法...仍在搜索中!
如果你总是得到两次发射,一个廉价的答案可能是使用标志来区分第一次和第二次
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,对 class 中的数据数组进行简单排序,但我没有从 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]);
然后我们可以捕获重复事件。
编辑 2022
此答案现已过时
Dragula 以前使用 EventEmitter,现在使用 Observable。请查看最新文档。
所以,在这一切之后,订阅 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 的评论,我进一步调查了上述代码解决问题的原因。它没有使用 asObservable() 来执行此操作,因此我不太确定为什么在 link 中建议我在上面提供的问题 714 中这样做。可能需要 asObservable() 以便我们可以正确取消订阅(假设 dragulaService 是一个 EventEmitter)?
以前我用的是我读到的in the docs:
dragulaService.destroy(name)
Destroys a drake instance named name.
摧毁'drake':
ngOnDestroy(){
this._dragulaService.destroy('bag-container');
}
这并没有取消订阅 dragulaService。因此,当我导航回该组件时,它仍在发出事件,这就是我多次掉落的原因。
事实证明,订阅和销毁 不是推荐的方式 使用 dragulaService,所以我已经提交 this PR 更新 ng2 的自述文件- dragula 向未来的用户说明这一点。
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"); }