14

As the title says, I have a reactive form that has multiple <mat-select> contained within. On initial form load, the initial option is not displayed even though form.value shows it is.

Pertinent component.ts:

export class DesJobInfoEditComponent implements OnInit {

...

currentJobData: IJob;
jobTypes: IJobType[];

...

constructor(private fb: FormBuilder) {

    ...

        // Construct forms
        this.createForm();

        this.initializeForm();

}

createForm() {
    this.editJobInfoForm = this.fb.group({
        ...
        JobType: '', // mat-select
        ...
    });
}

initializeForm() {
    this.rebuildForm();
}

rebuildForm() {
    this.editJobInfoForm.reset({
        ...
        JobType: this.jobTypes[this.currentJobData.JobType].DisplayDesc,
        ...
    });
}

}

Pertinent html:

<mat-form-field fxFlex>
      <mat-label>Job Type</mat-label>
       <mat-select formControlName="JobType" placeholder="Job Type">
              <mat-option *ngFor="let jobType of jobTypes" value="jobType.value">
                     {{ jobType.DisplayDesc }}
               </mat-option>
        </mat-select>
  </mat-form-field>

When the form loads, the selects do not display the initially selected option, however, they are set properly, apparently:

Form value { ... "JobType": "0 - Standard", ... } 

All that displays on the form is the placeholder.

This seems like it should not be this difficult.

What am I doing wrong?

EDIT:

this.jobTypes is loaded when the module is loaded, and it is a BehaviorSubject that lives in my data service. I subscribe to it in the constructor of this component thusly:

this.data.jobTypes.subscribe(jobTypes => { this.jobTypes = jobTypes });

4
  • Maybe you need to pipe async? let jobType of jobTypes | async Commented May 3, 2018 at 19:36
  • How are you loading jobTypes? I don't see the code that populates this.jobTypes Commented May 3, 2018 at 19:36
  • @Stanton I have updated my question. Commented May 3, 2018 at 19:41
  • have you had any updates on this? I'm having the exact same problem... When I change to a simple HTML select everything works fine... <select formControlName="category" [compareWith]="compareFn"> <option [ngValue]="c" *ngFor="let c of categoriesService.categories$ | async"> {{c.name}}</option> </select> Commented Sep 25, 2018 at 2:02

4 Answers 4

13

A few things

  1. [formControlName] must be used in conjunction with [formGroup]. If you don't want to use [formControlName] + [formGroup], you can use [formControl] instead.

  2. In angular, there is a difference between specifying an attribute as value and [value]. When an attribute is enclosed in brackets [], it is interpreted as javascript / angular template script (same as {{}}, I think). When it is not enclosed in brackets, it is interpreted as a string (i.e. value="jobType.value" === [value]="'jobType.value'" and [value]="jobType.value" === value="{{jobType.value}}" (actually I think there are subtle differences between [value]="jobType.value" and value="{{jobType.value}}", but w/e)). So when you write <mat-option *ngFor="let jobType of jobTypes" value="jobType.value">, the value of every mat-option is "jobType.value" which, I imagine, isn't want you want. So you need to change the code to <mat-option *ngFor="let jobType of jobTypes" [value]="jobType.value">

e.g.

<mat-form-field [formGroup]='editJobInfoForm' fxFlex>
  <mat-label>Job Type</mat-label>
  <mat-select formControlName="JobType" placeholder="Job Type">
    <mat-option *ngFor="let jobType of jobTypes" [value]="jobType.value">
      {{ jobType.DisplayDesc }}
    </mat-option>
  </mat-select>
</mat-form-field>

Somewhat unrelated to your problem, why have both createForm() and initializeForm() methods? Why not simply

constructor(private fb: FormBuilder) {

    ...

        // Construct forms
        this.createForm();    
}

createForm() {
    this.editJobInfoForm = this.fb.group({
        ...
        JobType: this.jobTypes[this.currentJobData.JobType].DisplayDesc,
        ...
    });
}
Sign up to request clarification or add additional context in comments.

1 Comment

Very well explained – my compliments!
7

Angalural Material compareWith: (o1: any, o2: any) => boolean from (API reference for Angular Material select)

component.ts

 export class ParentOneComponent implements OnInit {
  materialFormSample: FormGroup;
  constructor() { }

  ngOnInit() {
    this.configureMaterialFormSample();
  }
  name: string = 'Emilius Patrin Mfuruki';
  toppingList: string[] = ['Extra cheese', 'Mushroom', 'Onion', 'Pepperoni', 'Sausage', 'Tomato'];
  selectedToppingList = ['Extra cheese', 'Tomato', 'Onion'];

  compareWithFunc = (a: any, b: any) => a == b;

  configureMaterialFormSample() {
    this.materialFormSample = new FormGroup({
      name: new FormControl(this.name),
      toppings: new FormControl(this.selectedToppingList)
    })
  }
  onSubmitForm() {
    if (this.materialFormSample.valid) {
      console.log('submitting Form Content Valid', this.materialFormSample.value);
    }
  }
}

component.html

<form [formGroup]="materialFormSample" (ngSubmit)="onSubmitForm()" autocomplete="off">
      <mat-form-field class="w-100">
        <input matInput placeholder="Name" formControlName="name">
      </mat-form-field>
      <mat-form-field class="w-100">
        <mat-label>Toppings</mat-label>
        <mat-select formControlName="toppings" multiple [compareWith]="compareWithFunc">
          <mat-option *ngFor="let topping of toppingList" [value]="topping">{{topping}}</mat-option>
        </mat-select>
      </mat-form-field>
      <div class="clearfix">
        <button type="submit" mat-raised-button color="primary" class="float-right">Submit Form</button>
      </div>
    </form>

Comments

4

Use compare with function as it is ...

Step1: put [compareWith] tag as it is shown

<mat-form-field>
  <mat-select placeholder="Pick item..." formControlName="selectedItem" 
    [compareWith]="compareFn">
    <mat-option *ngFor="let item of items">
        {{item.name}}
    </mat-option>
  </mat-select>
</mat-form-field>
<button (click)="changeValue()">Change value</button>

Stet 2 : declare variable compareFn and function compareByValue as shown below no changes required

compareFn: ((f1: any, f2: any) => boolean) | null = this.compareByValue;

compareByValue(f1: any, f2: any) { 
  return f1 && f2 && f1.name === f2.name; 
}

Comments

2

Hi I know its too much late but I know a simpler solution. Your U I (html) components are described correctly

in your typescript you are comparing object with string which will not produce any validation. So always compare objects with null (in validation)

simply in createForm() { this.editJobInfoForm = this.fb.group({ ... JobType: '', // mat-select ... }); }

change JobType: '' replace with JobType:[null]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.