Angular Material Autocomplete closePanel方法失效问题求助
我正在开发一个带对话框的表单,其中一个表单项使用了mat-autocomplete。提交后的表单会添加到表格中,点击表格行时会重新打开表单对话框进行编辑。
当前问题
打开编辑表单时,autocomplete字段已填充之前提交的值,但autocomplete面板会自动展开,不符合预期。
期望效果
字段正常填充历史值,autocomplete面板保持关闭状态。
已尝试的方案
- 使用
@ViewChild(MatAutocompleteTrigger) autoComplete: MatAutocompleteTrigger;,调用this.autoComplete.closePanel(); - 在HTML的input上尝试
#autoComplete或#MatAutocompleteTrigger选择器 - 尝试过以下写法:
HTML:
TS:<input #autoCompleteInput type="text" [matAutocomplete]="auto"/>@ViewChild('autoCompleteInput', { read: MatAutocompleteTrigger }) autoComplete: MatAutocompleteTrigger;
但实际运行报错:ERROR TypeError: Cannot read properties of undefined (reading 'closePanel'),因为autoComplete实例为undefined。
以下是相关代码片段:
HTML(仅autocomplete部分)
<mat-form-field class="top-form-field" appearance="outline"> <mat-label>Brigada</mat-label> <input matInput #autoComplete aria-label="Brigada" [matAutocomplete]="auto" formControlName="nome_brigada"> <mat-autocomplete #auto="matAutocomplete"> <mat-option *ngFor="let brigade of filteredBrigades | async" [value]="brigade.name"> <img class="option-img" aria-hidden [src]="brigade.typeIcon" height="25"> <span>{{brigade.name}}</span> | <small>{{brigade.category}}</small> <span *ngIf="brigade.plate"> | <small>{{brigade.plate}}</small> </span> </mat-option> </mat-autocomplete> </mat-form-field>
TS代码(不含导入)
注:当row不为null时,为编辑已提交表单的场景(即需要关闭面板的场景)
export interface Brigade { type: string; typeIcon: string; name: string; category: string; filterString: string; id_brigada: string; plate: string; } @Component({ selector: 'select-brigade-dialog', templateUrl: 'select-brigade-dialog.html', }) export class SelectBrigadeDialog { @ViewChild(MatAutocompleteTrigger) autoComplete: MatAutocompleteTrigger; public brigadeForm: FormGroup; brigadeCtrl = new FormControl('', [Validators.required]); filteredBrigades: Observable<Brigade[]>; public brigades: Brigade[] = []; public tipos_brigada:string[]; public categorias_brigada:string[]; public nomes_brigada: string[]; combateChecked: boolean; rescaldoChecked: boolean; constructor( @Inject(MAT_DIALOG_DATA) public data: any, public fb: FormBuilder, private dtValidators: DatetimeValidators, private amplitude: AmplitudeService ) { this.combateChecked = false; this.rescaldoChecked = false; for (let b of data.brigadas){ this.brigades.push({ type: b['tipo_brigada'], typeIcon: brigadeIconMap[b['id_tipo_brigada']], name: b['nome_brigada'], category: b['categoria_brigada'], filterString: b['nome_brigada'] + b['placa'] + b['categoria_brigada'], id_brigada: b['id_brigada'], plate: b['placa'] }) } this.brigades.sort((a, b) => a.name.localeCompare(b.name)); if (data.row === null){ this.filterBrigades(''); } else { this.filterBrigades(data.row['nome_brigada']); } let id_acionamento = `${data.id_report}_${data.len_acionamentos}` this.initForm(data.row, id_acionamento); } filterBrigades(nome_brigada) { this.filteredBrigades = this.brigadeCtrl.valueChanges.pipe( startWith(nome_brigada), map(brigade => (brigade ? this._filterBrigades(brigade) : this.brigades.slice())), ); } private _filterBrigades(value: string): Brigade[] { const filterValue = value.toLowerCase(); return this.brigades.filter(brigade => brigade.filterString.toLowerCase().includes(filterValue)).sort((a, b) => a.name.localeCompare(b.name)); } initForm(row, id_acionamento){ let datetime_now = formatDate(new Date(), 'yyyy-MM-ddTHH:mm', 'en-US'); let dt_midnight = this.setMidnight(); let dt_validators = [ this.dtValidators.dtAcionamento(), this.dtValidators.dtCombate(), this.dtValidators.dtRescaldo(), this.dtValidators.dtChegadaCombate(), this.dtValidators.dtChegadaRescaldo(), this.dtValidators.dtFuturo(dt_midnight) ] if (row === null){ this.brigadeForm = this.fb.group({ nome_brigada: this.brigadeCtrl, contagem_equipe:['1',[Validators.required, Validators.min(1)]], dt_acionamento:[`${datetime_now}`,[Validators.required]], dt_chegada:[''], combate: false, dt_inicio_combate:[''], dt_fim_combate:[''], rescaldo: false, dt_inicio_rescaldo:[''], dt_fim_rescaldo:[''], id_acionamento:[id_acionamento], id_brigada: [''] },{ validators: dt_validators, }); } else { if (row['dt_inicio_combate'] !== null && row['dt_inicio_combate'] !== ''){ this.combateChecked = true; }; if (row['dt_inicio_rescaldo'] !== null && row['dt_inicio_rescaldo'] !== ''){ this.rescaldoChecked = true; }; this.brigadeForm = this.fb.group({ nome_brigada: [row['nome_brigada'],[Validators.required]], contagem_equipe:[row['contagem_equipe'],[Validators.required, Validators.min(1)]], dt_acionamento:[row['dt_acionamento'],[Validators.required]], dt_chegada:[row['dt_chegada']], combate: this.combateChecked, dt_inicio_combate:[row['dt_inicio_combate']], dt_fim_combate:[row['dt_fim_combate']], rescaldo: this.rescaldoChecked, dt_inicio_rescaldo: [row['dt_inicio_rescaldo']], dt_fim_rescaldo: [row['dt_fim_rescaldo']], id_acionamento: [row['id_acionamento']], id_brigada: [row['id_brigada']] },{ validators: dt_validators, }); console.log('autocomplete: ', this.autoComplete) this.autoComplete.closePanel(); } }
解决方案
问题根源是组件构造函数执行时视图尚未初始化,@ViewChild无法获取到实例;同时编辑场景下startWith(nome_brigada)触发valueChanges,导致面板自动展开。按以下步骤修复:
修正
@ViewChild获取方式
对应HTML中input的#autoComplete模板变量,指定读取MatAutocompleteTrigger:@ViewChild('autoComplete', { read: MatAutocompleteTrigger }) autoComplete: MatAutocompleteTrigger;利用
AfterViewInit生命周期钩子
实现AfterViewInit接口,将视图相关操作移到ngAfterViewInit中执行(确保视图初始化完成):import { AfterViewInit } from '@angular/core'; export class SelectBrigadeDialog implements AfterViewInit { // ...其他代码 ngAfterViewInit(): void { if (this.data.row === null){ this.filterBrigades(''); } else { this.filterBrigades(this.data.row['nome_brigada']); } let id_acionamento = `${this.data.id_report}_${this.data.len_acionamentos}`; this.initForm(this.data.row, id_acionamento); // 编辑场景下延迟关闭面板,确保valueChanges触发完成 if (this.data.row) { setTimeout(() => { this.autoComplete?.closePanel(); }, 0); } } }调整构造函数逻辑
构造函数中只保留数据初始化操作,移除视图相关调用:constructor( @Inject(MAT_DIALOG_DATA) public data: any, public fb: FormBuilder, private dtValidators: DatetimeValidators, private amplitude: AmplitudeService ) { this.combateChecked = false; this.rescaldoChecked = false; for (let b of data.brigadas){ this.brigades.push({ type: b['tipo_brigada'], typeIcon: brigadeIconMap[b['id_tipo_brigada']], name: b['nome_brigada'], category: b['categoria_brigada'], filterString: b['nome_brigada'] + b['placa'] + b['categoria_brigada'], id_brigada: b['id_brigada'], plate: b['placa'] }) } this.brigades.sort((a, b) => a.name.localeCompare(b.name)); }
修改后,编辑场景下表单初始化完成后面板会自动关闭,且不会出现undefined报错。
内容的提问来源于stack exchange,提问作者ludobegins

