Angular 无法在打开编辑时填充反应式表单中的 mat-select 控件。

huangapple go评论55阅读模式
英文:

Angular unable to populate mat-select control in a reactive form when its opened for edit

问题

I'm here to help with the translation. Please let me know which parts you'd like translated.

英文:

I'm new to angular and i think i've wasted alot of time on a very simple step.

I have a very simple form that is used to create Question entity in database. Form appear to be working fine when creating a record. But when its opened to edit an existing record 'mat-selct' control fields are not being populated. These dropdowns are also being loaded from api.

Following is the code.

component.ts

import { Component, Inject, inject, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, FormGroupDirective, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Observable } from 'rxjs';
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 {

  // model: Question;
  @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.questionForm.reset();
          this.formDirective.resetForm();
          console.log('form resetted!')
        },
        error: (err: any) =>{
          console.error(err);
        }
      })
    }
  }
  
  // FormControl fields references for validation
  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 = '';
    }
}

Some Additional findings
Other inputs for shortText, text, TextInfo are being populate. Only Mat-select are not being loaded. I must be missin some thing silly as even the values for select are not populating, they are there as if i keep them blank when its opend for edit and just press save again. The values are being submitted and get persisted in DB.

I've also tried form.setValue() method but still no luck.

    // 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);
    }
  }

Any help or direction would be helpful. Thanks.

答案1

得分: 2

When deciding which option to mark as selected, Angular compares values by identity (===).

This doesn't work when editing form, because current form control value is not equal to any of the values from the list, ie. they are not the same objects (even though they have the same structure).

questionTypes are retrieved by calling this._quesTypeServ.getAll, and current form control value is data.questionType.

To make Angular compare values in some other way, you can use compareWith input of SelectControlValueAccessor.

We can compare values by id, because that property is unique for each of these objects:

compareFn(a: QuestionType, b: QuestionType): boolean {
    return a && b ? (a.id === b.id) : (a === b);
}

And in template:

<mat-select formControlName="questionType" [compareWith]="compareFn">
    <mat-option *ngFor="let typeOption of questionTypes" [value]="typeOption">
        {{typeOption.code}}
    </mat-option>
</mat-select>

Documentation about compareWith: https://angular.io/api/forms/SelectControlValueAccessor#customizing-option-selection

英文:

When deciding which option to mark as selected, Angular compares values by identity (===).

This doesn't work when editing form, because current form control value is not equal to any of the values from the list, ie. they are not the same objects (even though they have the same structure).

questionTypes are retrieved by calling this._quesTypeServ.getAll, and current form control value is data.questionType.

To make Angular compare values in some other way, you can use compareWith input of SelectControlValueAccessor.

We can compare values by id, because that property is unique for each of these objects:

compareFn(a: QuestionType, b: QuestionType): boolean {
    return a &amp;&amp; b ? (a.id === b.id) : (a === b);
}

And in template:

&lt;mat-select formControlName=&quot;questionType&quot; [compareWith]=&quot;compareFn&quot;&gt;
    &lt;mat-option *ngFor=&quot;let typeOption of questionTypes&quot; [value]=&quot;typeOption&quot;&gt; 
        {{typeOption.code}} 
    &lt;/mat-option&gt;
&lt;/mat-select&gt;

Documentation about compareWith: https://angular.io/api/forms/SelectControlValueAccessor#customizing-option-selection

huangapple
  • 本文由 发表于 2023年6月6日 01:30:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/76408758.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定