You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 18中ngx-mat-select-search除ALL外选项无法点击求助

Angular 18中ngx-mat-select-search多选下拉搜索后点击选项无响应问题

在Angular 18项目中使用ngx-mat-select-search为mat-select多选下拉菜单实现搜索功能时,遇到以下问题:搜索'ALL'后可正常选中该选项,但点击菜单中的其他模块选项时完全无响应。

问题演示

问题演示动图

现有代码

HTML代码

<mat-form-field appearance="fill" class="matformfield">
    <mat-label class="matlabel" *ngIf="!selectedModules.length">Select Modules</mat-label>
    <mat-select #drop multiple [(ngModel)]="selectedModules">
        <ngx-mat-select-search [formControl]="control" ></ngx-mat-select-search>
        <mat-select-trigger *ngIf="drop.value && drop.value.length">
            {{ drop.value.length === modules.length ? 'ALL' : drop.value}}
        </mat-select-trigger>
        <mat-option class="matoption" *ngFor="let module of filteredRecords | async" [value]="module" [disabled]="isAllSelected() && module !== 'ALL'" (click)="onClick(drop.options, module === 'ALL')">{{ module }}</mat-option>
    </mat-select> 
</mat-form-field>

TypeScript代码

import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatSelect, MatOption } from '@angular/material/select';
import { ReplaySubject, Subject, takeUntil, take, QueryList } from 'rxjs';

export class ModuleMenuComponent implements OnInit, OnDestroy {
  public control: FormControl = new FormControl();
  private _onDestroy = new Subject<void>();
  public filteredRecords: ReplaySubject<any[]> = new ReplaySubject<any[]>(1);

  @ViewChild('Select') select: MatSelect;

  modules: any[] = [
    'ALL', 'lorem', 'ipsum', 'domor', 'sit', 'amet'
  ];
  selectedModules: string[] = [];

  ngOnInit() {
    this.setInitialValue();
    this.control.setValue('');
    this.filteredRecords.next(this.modules.slice());

    this.control.valueChanges
      .pipe(takeUntil(this._onDestroy))
      .subscribe(() => {
        this.filterModulesMulti();
      });
  }

  ngOnDestroy() {
    this._onDestroy.next();
    this._onDestroy.complete();
  }

  private setInitialValue() {
    this.filteredRecords
      .pipe(take(1), takeUntil(this._onDestroy))
      .subscribe(() => {
        this.select.compareWith = (a: any, b: any) => a === b;
      });
  }

  private filterModulesMulti() {
    if (!this.modules) {
      return;
    }

    let search = this.control.value;
    if (!search) {
      this.filteredRecords.next(this.modules.slice());
      return;
    }
    else {
      search = search.toLowerCase();
    }
    if (search.length >= 2) {
      this.filteredRecords.next(
        this.modules.filter(module => module.toLowerCase().indexOf(search) > -1)
      );
    }
  }

  clearFields() {
    this.selectedModules = [];
  }

  isAllSelected(): boolean {
    return this.selectedModules.includes('ALL');
  }

  onClick(options:QueryList<MatOption>, isFirst:boolean=false) {
    if (isFirst) {
      const el = options.first;
      options.forEach((x) => {
        x[el.selected ? 'select' : 'deselect']();
      });
      if (el.selected) {
        this.selectedModules = this.modules.slice();
      }
      else {
        this.selectedModules = [];
      }
    }
    else {
      const selected = options.filter((x, index) => index && x.selected);
      if (selected.length == this.modules.length - 1) { 
        options.first.select();
        this.selectedModules = this.modules.slice(); 
      }
      else {
        options.first.deselect();
        this.selectedModules = selected.map(x => x.value);
      }
    }
  }
}

解决思路

1. 修复ViewChild选择器错误

HTML中mat-select的模板变量是#drop,但TS中@ViewChild('Select')的选择器不匹配,导致this.select始终为undefined,compareWith逻辑无法生效。将TS中的ViewChild改为:

@ViewChild('drop') select: MatSelect;

2. 修正过滤逻辑的漏洞

当前过滤逻辑仅在搜索字符长度≥2时才执行过滤,输入单个字符时列表不会更新,导致选项状态异常。移除长度判断,确保任何搜索输入都能触发过滤:

private filterModulesMulti() {
  if (!this.modules) return;

  let search = this.control.value?.toLowerCase() || '';
  
  this.filteredRecords.next(
    this.modules.filter(module => module.toLowerCase().includes(search))
  );
}

3. 移除手动点击事件,通过ngModel处理全选逻辑

手动操作MatOption的select()/deselect()方法会与[(ngModel)]的双向绑定冲突,尤其是在过滤后选项列表变化时。改用selectedModules的 setter 处理全选逻辑:

private _selectedModules: string[] = [];

get selectedModules(): string[] {
  return this._selectedModules;
}

set selectedModules(value: string[]) {
  if (!value) {
    this._selectedModules = [];
    return;
  }

  // 处理全选逻辑
  if (value.includes('ALL')) {
    this._selectedModules = [...this.modules];
  } else {
    // 若选中所有非ALL选项,自动触发全选
    if (value.length === this.modules.length - 1) {
      this._selectedModules = [...this.modules];
    } else {
      // 移除ALL,避免手动选中其他选项时残留ALL
      this._selectedModules = value.filter(item => item !== 'ALL');
    }
  }
}

同时修改HTML,移除(click)事件和[disabled]属性:

<mat-option class="matoption" *ngFor="let module of filteredRecords | async" [value]="module">
  {{ module }}
</mat-option>

4. 确保组件生命周期正确实现

将初始化逻辑移至ngOnInit而非构造函数,并在ngOnDestroy中销毁订阅,避免内存泄漏(已在修正后的TS代码中体现)。

内容的提问来源于stack exchange,提问作者Rohan Buch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 04:00:59