Angular 7 - 虚拟滚动条在滚动时重置多选中的所选项目

问题描述 投票:0回答:2

我有一个多选下拉菜单,里面有虚拟滚动条。

HTML

<mat-form-field>
  <mat-label>Toppings</mat-label>
  <mat-select [formControl]="multiSelectControl" multiple [(value)]="selected" (openedChange)="openChange($event)">
    <cdk-virtual-scroll-viewport itemSize="5" minBufferPx="200" maxBufferPx="400" [style.height.px]=5*48>
      <button (click)="selectAll()">Select All</button>
      <button (click)="clear()">Clear</button>
      <mat-option *cdkVirtualFor="let topping of toppingList" [value]="topping">{{topping}}</mat-option>
    </cdk-virtual-scroll-viewport>
  </mat-select>
</mat-form-field>

.ts 文件:

export class AppComponent {
  title = 'test-proj';

  toppings = new FormControl();
  toppingList: string[] = ['Extra cheese', 'Mushroom', 'Onion', 'Pepperoni', 'Sausage', 'Tomato'];
  selected: any;

  @ViewChild(CdkVirtualScrollViewport)
  cdkVirtualScrollViewPort: CdkVirtualScrollViewport;

  multiSelectControl = new FormControl();

  constructor() {
    for (let i = 0; i < 4000; i++) {
      this.toppingList.push('gjkgkf--' + i);
    }
  }

  selectAll() {
    this.selected = this.toppingList;
    this.multiSelectControl.patchValue(this.toppingList);
  }

  clear() {
    this.selected = [];
    this.multiSelectControl.patchValue([]);
  }

  openChange($event: boolean) {
    if ($event) {
      this.cdkVirtualScrollViewPort.scrollToIndex(0);
      this.cdkVirtualScrollViewPort.checkViewportSize();
    }
  }

我在下拉列表中添加了 4000 个项目。如果我选择前两项和最后两项并滚动,则前两项的选择检查就会消失。

请提出建议。谢谢

angular angular-material scrollbar
2个回答
3
投票

我已经为错误本身创建了一种解决方法。它使用

onSelectionChange
元素的
mat-option
。最重要的是,它还会检查选项列表上的更改,并在必要时进行选择。这是未经优化的原始代码,但您会明白的:

@ViewChildren(MatOption)
options: QueryList<MatOption>;

constructor(private cd: ChangeDetectorRef) {}

ngAfterViewInit(): void {
  this.options.changes.subscribe(() => {
    let needUpdate = false;

    this.options.forEach((option) => {
      const selected = this.selected.includes(option.value);
       if (selected && !option.selected) {
        option.select();
        needUpdate = true;
      } else if (!selected && option.selected) {
        option.deselect();
        needUpdate = true;
      }
    });
    if (needUpdate) {
      this.cd.detectChanges();
    }
  });
}

onSelectionChange(change): void {
  if (!change.isUserInput) {
    return;
  }

  const value = change.source.value;
  const idx = this.selected.indexOf(change.source.value);

  if (idx > -1) {
    this.selected.splice(idx, 1)
  } else {
    this.selected.push(value);
  }
}

这就是

mat-select

<mat-select [formControl]="multiSelectControl" multiple [value]="selected" (openedChange)="openChange($event)">
  <cdk-virtual-scroll-viewport itemSize="5" minBufferPx="200" maxBufferPx="400" [style.height.px]=5*48>
    <button (click)="selectAll()">Select All</button>
    <button (click)="clear()">Clear</button>
    <mat-option *cdkVirtualFor="let topping of toppingList" [value]="topping" (onSelectionChange)="onSelectionChange($event)">{{topping}}</mat-option>
  </cdk-virtual-scroll-viewport>
</mat-select>

使用有效的 stackblitz


请注意,如果您有一个可以更新其内容的动态列表,并且已从该列表中删除了所做的选择,那么它仍然会被选择,即使这不再可能了


0
投票

首先,确保导入ScrollingModule。

我相信答案是向您的 *cdkVirtualFor 添加一个 trackBy 函数

*cdkVirtualFor="let topping of toppingList; trackBy: trackByName; templateCacheSize: 0"

trackByName(index: number, item: string): string {
    return item;
}

如果您有重复的名称,那么您可以尝试按索引进行跟踪,但我更喜欢为每个项目使用自定义 ID。一些能让他们独一无二的东西。

如果您正在处理动态数据,我还会添加

templateCacheSize: 0

© www.soinside.com 2019 - 2024. All rights reserved.