使用带有 Angular CDK DnD 的地图时如何更新视图?

How to update the view when using maps with Angular CDK DnD?

我有一个小清单,其中包含一些可以重新排序的项目 (see stackblitz)。在内部,该列表是作为地图实现的。方便的是,Angular 提供了管道键值,它允许一种简单的方法来遍历映射,如下所示:

*ngFor="let item of doc.items | keyvalue:sortHash"

您可以提供一个函数 sortHash 来负责对列表进行排序。 我想使用 cdkDropList 来为列表提供 DnD 排序。这在使用数组时很简单:

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

这对数组按预期工作,但在我的例子中,我依赖于顺序由 属性“顺序”定义的地图,请参阅我的数据结构:

  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 函数有点不同,它

  1. 将我的地图 (doc.items) 转换为数组
  2. 然后使用内置的moveItemInArray函数有效地移动数组中的项目
  3. 然后更新所有项目的“顺序”属性,最后
  4. 将数组转换回映射

排序功能正常,但UI在DnD时没有更新。

这里有一个stackblitz带有简化的示例代码。我在这里错过了什么?

答案就在Angular 核心深处。 您正在使用 KeyValuePipeSource here.

我们感兴趣的管道部分是这样的:

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

如果diff发现输入对象有差异,排序函数为运行。

您可以控制台日志 differChanges 并在视图初始化后看到它始终 returns null。为什么?我们需要看看 the differ code:

  // 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) 来确定值是否已更改。这种情况下要diff的对象的值是一个对象本身,Object.is()不计算深度相等。

所以你至少有两个选择:

  • 按照您希望的方式编写您自己的键值管道。
  • 使用不同的数据结构来保存您的电影信息

I've created a working StackBlitz 使用自定义 brute-force 求助策略 keyvalue 管道。