Angular响应式表单编辑时mat-select控件无法填充求助
Angular mat-select编辑时无法填充数据问题
我是Angular新手,在一个简单步骤上耗费了大量时间。我有一个用于在数据库中创建Question实体的表单,创建记录时功能正常,但打开编辑现有记录时,mat-select控件无法填充数据,这些下拉选项同样来自API接口。
补充发现:其他输入框(shortText、text、textInfo)均能正常填充,仅mat-select控件无数据显示。奇怪的是,即使编辑时不修改这些下拉框直接保存,原有值仍能正常提交并持久化到数据库。我尝试过使用form.setValue()方法,还尝试通过id匹配下拉数据后设置值,但依旧无效。
相关代码
component.ts
import { Component, Inject, OnInit, ViewChild } from '@angular/core'; import { FormBuilder, FormGroup, FormGroupDirective, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA } from '@angular/material/dialog'; import { Question } from 'src/app/Models/Question'; import { QuestionCategory } from 'src/app/Models/QuestionCategory'; import { QuestionType } from 'src/app/Models/QuestionType'; import { StResponse } from 'src/app/Models/StResponse'; import { QuestionCategoryService } from 'src/app/Services/question-category.service'; import { QuestionTypeService } from 'src/app/Services/question-type.service'; import { QuestionService } from 'src/app/Services/question.service'; @Component({ selector: 'app-question-form', templateUrl: './question-form.component.html', styleUrls: ['./question-form.component.scss'] }) export class QuestionFormComponent implements OnInit { @ViewChild(FormGroupDirective) formDirective!: FormGroupDirective; questionForm!: FormGroup questionTypes: QuestionType[] = [] questionCategories: QuestionCategory [] = []; constructor(private _fb: FormBuilder, private _service: QuestionService, private _quesCategoryServ: QuestionCategoryService, private _quesTypeServ: QuestionTypeService, @Inject(MAT_DIALOG_DATA) private data: Question ) { } ngOnInit(): void { console.log('QF Oninit', this.data); this._quesCategoryServ.getAll().subscribe((sr:StResponse)=>{ console.log('QesComp:', sr.data) this.questionCategories = sr.data.QuestionCategory; }); this._quesTypeServ.getAll().subscribe((sr:StResponse)=>{ console.log('QesComp:', sr.data) this.questionTypes = sr.data.QuestionType; }); this.questionForm = this._fb.group({ shortText: ['',Validators.required], text: ['',Validators.required], textInfo: [''], questionType: [null,Validators.required], questionCategory:[null,Validators.required], }); if(this.data){ console.log('oninit-forEdit') this.questionForm.patchValue({ shortText: this.data.shortText, text: this.data.text, textInfo: this.data.textInfo, questionType: this.data.questionType, questionCategory: this.data.questionCategory, }); } } onFormSubmit():void { if(this.questionForm.valid){ console.log('submited form values',this.questionForm.value); this._service.add(this.questionForm.value).subscribe({ next: (val: StResponse) => { alert('Question Added') this.formDirective.resetForm(); console.log('form resetted!') }, error: (err: any) =>{ console.error(err); } }) } } get shortText() { return this.questionForm.get('shortText'); } get text() { return this.questionForm.get('text'); } get textInfo() { return this.questionForm.get('textInfo'); } get questionType() { return this.questionForm.get('questionType'); } get questionCategory() { return this.questionForm.get('questionCategory'); } }
component.html
<h1 mat-dialog-title>Question</h1> <form [formGroup]="questionForm" (ngSubmit)="onFormSubmit()"> <div mat-dialog-content class="content"> <div class="row"> <mat-form-field appearance="outline"> <mat-label>Short Text</mat-label> <input matInput type="text" placeholder="Ex. Wheels." formControlName="shortText"> <mat-error *ngIf="shortText?.invalid">Enter short text.</mat-error> </mat-form-field> </div> <div class="row"> <mat-form-field appearance="outline"> <mat-label>Question</mat-label> <input matInput type="text" placeholder="Ex. Undamaged, All nuts tight." formControlName="text"> <mat-error *ngIf="text?.invalid">Please enter question text.</mat-error> </mat-form-field> </div> <div class="row"> <mat-form-field appearance="outline"> <mat-label>Details</mat-label> <textarea matInput placeholder="Ex. Any details to further explain this question." formControlName="textInfo"></textarea> </mat-form-field> </div> <div class="row"> <mat-form-field appearance="outline"> <mat-label>Type</mat-label> <mat-select formControlName="questionType" > <mat-option *ngFor="let typeOption of questionTypes" [value]="typeOption"> {{typeOption.code}} </mat-option> </mat-select> <mat-error *ngIf="questionType?.invalid">You must make a selection</mat-error> </mat-form-field> </div> <div class="row"> <mat-form-field appearance="outline"> <mat-label>Category</mat-label> <mat-select formControlName="questionCategory" > <mat-option *ngFor="let categoryOption of questionCategories" [value]="categoryOption"> {{categoryOption.code}} </mat-option> </mat-select> <mat-error *ngIf="questionCategory?.invalid">You must make a selection</mat-error> </mat-form-field> </div> </div> <div mat-dialog-actions class="action"> <button mat-raised-button mat-dialog-close>Close</button> <button mat-raised-button color="primary" type="submit">save</button> </div> </form>
模型类定义
export class Question{ id: number; shortText: string; text: string; textInfo: string; questionType: QuestionType; questionCategory: QuestionCategory; constructor(){ this.id = 0; this.shortText = ''; this.text = ''; this.textInfo = ''; this.questionType = new QuestionType(); this.questionCategory = new QuestionCategory(); } } export class QuestionType{ id: number; code: string; constructor(){ this.id = 0; this.code = ''; } } export class QuestionCategory{ id: number; code: string; name: string; constructor(){ this.id = 0; this.code = ''; this.name = ''; } }
尝试过的无效修改
// part of ngOnInit() if(this.data){ console.log('oninit-forEdit') this.questionForm.patchValue({ shortText: this.data.shortText, text: this.data.text, textInfo: this.data.textInfo, // questionType: this.data.questionType, // questionCategory: this.data.questionCategory, }); const selectedCategory = this.questionCategories.find(category => category.id === this.data.questionCategory.id); const selectedType = this.questionTypes.find(type => type.id === this.data.questionType.id); this.questionForm.get('questionCategory')?.setValue(selectedCategory); this.questionForm.get('questionType')?.setValue(selectedType); }
问题原因
- 异步时序问题:API请求是异步执行的,你在
ngOnInit里先发起请求,然后立刻执行表单赋值操作,此时questionCategories和questionTypes还是空数组,找不到匹配的选项,导致设置值无效。 - 对象引用匹配问题:Angular的
mat-select默认通过对象引用判断选中项,即使后端返回的data.questionType和下拉选项的属性完全一致,但两个对象的内存引用不同,也不会被识别为选中项。
解决方案
方案1:确保下拉数据加载完成后再赋值
使用forkJoin合并两个API请求,等所有下拉数据都返回后再初始化表单并赋值:
import { forkJoin } from 'rxjs'; // 导入forkJoin ngOnInit(): void { console.log('QF Oninit', this.data); // 合并两个API请求,等待所有请求完成 forkJoin([ this._quesCategoryServ.getAll(), this._quesTypeServ.getAll() ]).subscribe(([categoryRes, typeRes]) => { this.questionCategories = categoryRes.data.QuestionCategory; this.questionTypes = typeRes.data.QuestionType; // 初始化表单 this.initForm(); // 编辑模式下赋值 if(this.data){ this.setFormValues(); } }); } // 抽离表单初始化逻辑 initForm(): void { this.questionForm = this._fb.group({ shortText: ['',Validators.required], text: ['',Validators.required], textInfo: [''], questionType: [null,Validators.required], questionCategory:[null,Validators.required], }); } // 抽离表单赋值逻辑 setFormValues(): void { this.questionForm.patchValue({ shortText: this.data.shortText, text: this.data.text, textInfo: this.data.textInfo, }); // 从已加载的下拉数据中匹配对象(保证引用一致) const selectedCategory = this.questionCategories.find(category => category.id === this.data.questionCategory.id); const selectedType = this.questionTypes.find(type => type.id === this.data.questionType.id); this.questionForm.get('questionCategory')?.setValue(selectedCategory); this.questionForm.get('questionType')?.setValue(selectedType); }
方案2:使用compareWith函数(推荐)
如果不想依赖请求时序,或者后端返回的对象和下拉选项引用不一致,可以通过compareWith函数自定义匹配规则(比如用id),让mat-select根据规则识别选中项:
- 在component.ts中添加比较函数:
// 比较QuestionType的匹配规则 compareQuestionType(a: QuestionType, b: QuestionType): boolean { return a && b ? a.id === b.id : a === b; } // 比较QuestionCategory的匹配规则 compareQuestionCategory(a: QuestionCategory, b: QuestionCategory): boolean { return a && b ? a.id === b.id : a === b; }
- 在component.html的
mat-select上绑定compareWith:
<!-- 修改Type的mat-select --> <mat-select formControlName="questionType" [compareWith]="compareQuestionType"> <mat-option *ngFor="let typeOption of questionTypes" [value]="typeOption"> {{typeOption.code}} </mat-option> </mat-select> <!-- 修改Category的mat-select --> <mat-select formControlName="questionCategory" [compareWith]="compareQuestionCategory"> <mat-option *ngFor="let categoryOption of questionCategories" [value]="categoryOption"> {{categoryOption.code}} </mat-option> </mat-select>
这样修改后,即使API请求还没返回就设置了表单值,等下拉数据加载完成后,mat-select会自动通过compareWith函数匹配到正确选项,同时也解决了对象引用不一致的问题。
内容的提问来源于stack exchange,提问作者AlphaTry
相关产品推荐
相关产品推荐

