Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 5 + Angular Material Select + Reactive Forms == No initial options displayed

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

like image 829
OCDDev Avatar asked May 03 '18 19:05

OCDDev


Video Answer


2 Answers

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>
like image 188
Emilius Mfuruki Avatar answered Oct 14 '22 22:10

Emilius Mfuruki


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,
        ...
    });
}
like image 23
John Avatar answered Oct 14 '22 23:10

John