我有一个小列表,其中有一些项目可以重新排序(参见stackblitz)。在内部,该列表是作为映射实现的。方便的是,Angular提供了管道keyvalue,它允许像这样简单地迭代映射:
*ngFor="let item of doc.items | keyvalue:sortHash"
您可以提供一个函数sortHash,它负责对列表进行排序。我想用cdkDropList
,以便给列表提供nd排序。这在使用数组时是微不足道的:
(cdkDropListDropped)="dropItem($event, doc.items, doc)
您必须简单地将一个函数传递给cdkDropListDropped
,该函数将负责在数组中移动项。Angular提供了一个内置函数moveItemInArray
:
import { moveItemInArray } from '@angular/cdk/drag-drop';
...
async dropItem(event: CdkDragDrop<string[]>, list: any, doc: any) {
moveItemInArray(list, event.previousIndex, event.currentIndex);
}
这在数组中可以正常工作,但在我的情况下,我依赖于映射,其中顺序由属性"order"定义,请参阅我的数据结构:
doc = {
meta: {
text: 'title',
...
},
items: {
SEC_000000: {
meta: {
text: 'Episode I - The Phantom Menace',
order: '0',
...
},
},
SEC_111111: {
meta: {
text: 'Episode II - Attack of the Clones',
order: '1',
...
},
},
SEC_222222: {
meta: {
text: 'Episode III - Revenge of the Sith',
order: '2',
...
},
},
},
};
因此我的dropItem
函数有点不同,它
- 将我的地图(
doc.items
)转换为数组 - 然后使用内置的
moveItemInArray
函数有效地移动数组内的项目 - 则更新"订单";属性,最后是
- 将数组转换回地图
排序功能按预期工作,但当免打扰时UI不更新。
下面是一个简化的stackblitz示例代码。我遗漏了什么?
答案就在Angular的核心深处。您正在使用KeyValuePipe
。源。
我们感兴趣的部分是:
const differChanges: KeyValueChanges<K, V>|null = this.differ.diff(input as any);
const compareFnChanged = compareFn !== this.compareFn;
if (differChanges || compareFnChanged) {
this.keyValues.sort(compareFn);
this.compareFn = compareFn;
}
如果在输入对象中发现了差异,则运行排序函数。
您可以控制日志differChanges
,并看到它在视图初始化后始终返回null
。为什么?我们需要查看不同的代码:
// Add the record or a given key to the list of changes only when the value has actually changed
private _maybeAddToChanges(record: KeyValueChangeRecord_<K, V>, newValue: any): void {
if (!Object.is(newValue, record.currentValue)) {
record.previousValue = record.currentValue;
record.currentValue = newValue;
this._addToChanges(record);
}
}
不同之处在于使用Object.is(newValue, record.currentValue)
来确定值是否已更改。在这种情况下,要区分的对象的值是对象本身,并且Object.is()
不计算深度相等。
所以你至少有两个选择:
- 写你自己的键值管道,以你想要的方式工作。
- 使用不同的数据结构来保存电影信息
我已经创建了一个工作StackBlitz自定义暴力破解策略keyvalue
管道。