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

ngx-mat-select-search搜索后选中项返回初始列表第一项问题求助

ngx-mat-select-search选中项异常:点击选中后返回初始列表第一项

我配置的ngx-mat-select-search可以正常过滤选项,但点击选中某一项时,返回的却是初始列表的第一项。我参考了单选示例,但我的场景使用了FormBuilder。以下是相关代码和问题演示:

问题演示动图

HTML代码

<mat-select [formControl]="orgaCtrl" *ngIf="ogListe"  placeholder="Sélectionner un organisme"
    #singleSelect class="form-control organisme" formControlName="id_organisme">
    <mat-option>
        <ngx-mat-select-search 
            [formControl]="orgFilterCtrl"
            placeholderLabel="Rechercher un organisme ..."
        >
        <span ngxMatSelectNoEntriesFound>
            Aucun OG trouvé
            <button mat-button color="primary" (click)="nouvelOg()">
                Ajouter votre organisme <mat-icon>add</mat-icon>
            </button>
        </span>
        </ngx-mat-select-search>
    </mat-option>
    <mat-option *ngFor="let og of filteredOrgs | async" [value]="og.id_organisme">
        {{og.nom_organisme}}
    </mat-option>
</mat-select>

TypeScript代码

import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { FormGroup, FormBuilder, Validators, ValidatorFn, AbstractControl, FormControl } from '@angular/forms';
import { MatSelect } from '@angular/material/select';
import { Router } from '@angular/router';

import { ToastrService } from 'ngx-toastr';
import { ReplaySubject, Subject, take, takeUntil } from 'rxjs';
import { AuthService } from '../auth.service';

import { Organisme } from '../models/models';

@Component({
  selector: 'app-sign-up',
  templateUrl: './sign-up.component.html',
  styleUrls: ['./sign-up.component.scss']
})

export class SignUpComponent implements OnInit, AfterViewInit, OnDestroy {
  form: FormGroup;
  appFormGroup: FormGroup;
  public disableSubmit = false;
  public formControlBuilded = false;
  protected organismes: Organisme[];
  searchTxt: any;
  ogListe : boolean = true;

  public orgaCtrl: FormControl = new FormControl();
  public orgFilterCtrl: FormControl = new FormControl();
  public filteredOrgs: ReplaySubject<Organisme[]> = new ReplaySubject<Organisme[]>(1);

  @ViewChild('singleSelect') singleSelect: MatSelect;
  protected _onDestroy = new Subject<void>();

  constructor(
    private fb: FormBuilder,
    private _authService: AuthService,
    private router: Router,
    private _toasterService: ToastrService
  ) {
  }

  ngOnInit() {
    this.createForm();
    this._authService.getOrganismes().subscribe(
      res => {
        this.organismes = res
        this.filteredOrgs.next(this.organismes.slice());
        this.orgFilterCtrl.valueChanges
      .pipe(takeUntil(this._onDestroy))
      .subscribe(() => {
        this.filterOrgs();
      });
      }
    );
  }

  ngAfterViewInit() {
    this.setInitialValue();
  }

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

  protected setInitialValue() {
    this.filteredOrgs
      .pipe(take(1), takeUntil(this._onDestroy))
      .subscribe(() => {
        this.singleSelect.compareWith = (a: Organisme, b: Organisme) => a && b && a.id_organisme === b.id_organisme;
      });
  }

  protected filterOrgs() {
    if (!this.organismes) {
      return;
    }
    let search = this.orgFilterCtrl.value;
    if (!search) {
      this.filteredOrgs.next(this.organismes.slice());
      return;
    } else {
      search = search.toLowerCase();
    }
    this.filteredOrgs.next(
      this.organismes.filter(organisme => organisme.nom_organisme.toLowerCase().indexOf(search) > -1)
    );
  }

  createForm() {
    this.form = this.fb.group({
      nom_role: ['', Validators.required],
      prenom_role: ['', Validators.required],
      identifiant: ['', Validators.required],
      email: [
        '',
        [Validators.email, Validators.required],
      ],
      password: ['', [Validators.required]],
      password_confirmation: ['', [Validators.required]],
      remarques: ['', Validators.required],
      id_organisme: ['', Validators.required],
      organisme: ['', Validators.required],
    },
    {
      validators: [
        this.ConfirmedValidator('password','password_confirmation'),
        this.atLeastOneRequired('id_organisme', 'organisme')
      ]
    });
    this.appFormGroup = this.fb.group({
      geonature_saisie: [false, null],
      psdrf: [false, null],
      ancrage: [false, null],
      opnl: [false, null],
      precisions: ['', null]
    });
  }

  save() {
    if (this.form.valid) {
      this.disableSubmit = true;
      const finalForm = Object.assign({}, this.form.value);
      finalForm['champs_addi'] = this.appFormGroup.value;
      this._authService
        .signupUser(finalForm)
        .subscribe((res) => {
          this._toasterService.info('Vous recevrez un mail de confirmation quand elle aura été validée par un administrateur.','Votre demande d\'inscription a bien été prise en compte !')
          this.form.reset();
          this.appFormGroup.reset();
          window.scroll({ 
            top: 0, 
            left: 0, 
            behavior: 'smooth' 
     });
        },
        error => {
          this._toasterService.error(error.error.msg, '');
        })
        .add(() => {
          this.disableSubmit = false;
        });
    }
  }

  ConfirmedValidator(controlName: string, matchingControlName: string) {
    return (formGroup: FormGroup) => {
      const control = formGroup.controls[controlName];
      const matchingControl = formGroup.controls[matchingControlName];
      if (
        matchingControl.errors &&
        !matchingControl.errors['confirmedValidator']
      ) {
        return;
      }
      if (control.value !== matchingControl.value) {
        matchingControl.setErrors({ confirmedValidator: true });
      } else {
        matchingControl.setErrors(null);
      }
    };
  }

  atLeastOneRequired(valueName1: string, valueName2: string) {
    return (formgroup: FormGroup) => {
      const value1 = formgroup.controls[valueName1];
      const value2 = formgroup.controls[valueName2];
      if (value1.value || value2.value) {
        value1.setErrors(null);
        value2.setErrors(null);
      } else {
        value1.setErrors({ atLeastOneRequired: true });
        value2.setErrors({ atLeastOneRequired: true });
      }
    }
  }

  nouvelOg() {
    this.ogListe = false;
    this.form.get('id_organisme')!.patchValue('');
  }
}

问题原因分析

  1. 控件绑定冲突:mat-select同时设置了[formControl]="orgaCtrl"和formControlName="id_organisme",双重绑定导致选中值的更新逻辑混乱,表单无法正确感知用户选择的项。
  2. compareWith逻辑不匹配:mat-option的[value]绑定的是og.id_organisme(数值类型),但compareWith函数却在比较Organisme对象,类型不匹配导致无法正确匹配选中项,最终默认取过滤后列表的第一项。

解决方案

1. 移除冲突的独立控件绑定

删除mat-select上的[formControl]="orgaCtrl",直接使用表单组中的id_organisme控件:

<mat-select *ngIf="ogListe" placeholder="Sélectionner un organisme"
    #singleSelect class="form-control organisme" formControlName="id_organisme">
    <!-- 保留其他内容不变 -->
</mat-select>

2. 调整compareWith函数逻辑

将compareWith修改为比较数值类型的id值,匹配mat-option的value类型:

protected setInitialValue() {
    this.filteredOrgs
      .pipe(take(1), takeUntil(this._onDestroy))
      .subscribe(() => {
        this.singleSelect.compareWith = (a: number, b: number) => a === b;
      });
}

3. 删除无用的独立控件定义

移除TS中独立的orgaCtrl变量,因为现在直接使用表单组的控件:

// 删除此行代码
// public orgaCtrl: FormControl = new FormControl();

修改完成后,点击过滤后的选项时,表单的id_organisme控件会正确获取选中项的id值,不再出现选中第一项的异常。

内容的提问来源于stack exchange,提问作者Zacharie Moulin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 18:47:22