Components: select component - bug when used with reactive form - not usable inside formGroup

Created on 1 Mar 2018  路  17Comments  路  Source: angular/components

Bug

when using a select component inside a reactive form, and updating the selection using the select component, the corresponding formControl is not being updated,

say I have a component that is inside formGroup:

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

component:

{

@ViewChild(MatSelect)selectComponent:MatSelect;


     ngOnInit(){
      this.formGroup = this.formBuilder.group({
            selectedItem: ['', Validators.required],
      });

    this.items = [{name:'a'},{name:'b'},{name:'c'}];



}
   changeValue(){
     this.selectComponent.value = this.items[1];
   } 
}

when I press the "Change value" button, the selectComponent.value is being set,
and the changes do reflect on the ui, but the corresponding formControl(selectedItem) is not being set
and the form is invalid (the field is required)

What is the expected behavior?

the formControl should be updated with the value being set on the select component

What is the current behavior?

the formControl is not being updated

What are the steps to reproduce?

Providing a StackBlitz reproduction is the best way to share your issue.

StackBlitz starter: https://goo.gl/wwnhMV

What is the use-case or motivation for changing an existing behavior?

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

Angular CLI: 1.7.2
Node: 8.9.1
OS: win32 x64
Angular: 5.2.7
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

@angular/cdk: 5.2.3
@angular/cli: 1.7.2
@angular/material-moment-adapter: 5.2.3
@angular/material: 5.2.3
@angular-devkit/build-optimizer: 0.3.2
@angular-devkit/core: 0.3.2
@angular-devkit/schematics: 0.3.2
@ngtools/json-schema: 1.2.0
@ngtools/webpack: 1.10.1
@schematics/angular: 0.3.2
@schematics/package-update: 0.3.2
typescript: 2.5.3
webpack: 3.11.0

Is there anything else we should know?

P4 materiaform-field materiaselect has pr

Most helpful comment

As the options are objects, it's normal that the comparison fails when you select programmatically the value. You need to set a compareWith function (take a look at https://github.com/angular/material2/issues/6970#issuecomment-328355857):

<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>
compareFn: ((f1: any, f2: any) => boolean) | null = this.compareByValue;

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

All 17 comments

As the options are objects, it's normal that the comparison fails when you select programmatically the value. You need to set a compareWith function (take a look at https://github.com/angular/material2/issues/6970#issuecomment-328355857):

<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>
compareFn: ((f1: any, f2: any) => boolean) | null = this.compareByValue;

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

First of all, I'm using the same object when selecting - I have a list of items and I'm selecting one of them, so there should be no issue with comparison.

In my original code, I am using a compareWith input, but still having the same behavior, here is a more complete example that uses compareWith and is not working:

when I click the "selectOption" button, the ui changes, but the form control (reflected in the ui with the following html: <p class="col-auto selected-food"> Selected value: {{formGroup.get('favoriteFood').value | json}} </p> is not changing

html:
------------------

<form [formGroup]="formGroup" class="col-12">
 <div class="row mt-5 justify-content-center align-items-baseline">
   <mat-form-field class="col-auto">
     <mat-select [compareWith]="compareFn" formControlName="favoriteFood" placeholder="Favorite food">
       <mat-option *ngFor="let food of foods$ | async" [value]="food">
         {{food.viewValue}}
       </mat-option>
     </mat-select>
   </mat-form-field>

   <p class="col-auto selected-food"> Selected value: {{formGroup.get('favoriteFood').value | json}} </p>
 </div>

  <button mat-button (click)="openSelection()">Open</button>
  <button mat-button (click)="clearSelection()">Clear Selection</button>
  <button mat-button (click)="selectOption()">Select</button>
</form>
component:
-------------------------
import {Component, OnInit, QueryList, ViewChild, ViewChildren} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {MatOption, MatSelect} from '@angular/material';
import {Observable} from 'rxjs/Observable';
import {FoodProvider} from '../../../application-model/services/food-provider';
import {Food} from '../../../domain-model/entities/food';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss'],
  host:{
    class:'row justify-content-center'
  }
})
export class HomeComponent implements OnInit {


  @ViewChild(MatSelect) select: MatSelect;

  formGroup:FormGroup;
  foods$:Observable<Food[]>;

  constructor(private foodProvider:FoodProvider, private formBuilder:FormBuilder) { }


  compareFn(f1:Food, f2:Food):boolean{
    return f1.value == f2.value && f1.viewValue == f2.viewValue;
  }

  get selectedFood(): Food{
    return new Food(this.formGroup.get('favoriteFood').value);
  }

  ngOnInit() {
    this.formGroup = this.formBuilder.group({
      favoriteFood:['',[Validators.required]]
    });

    this.foods$ = this.foodProvider.getFood();

    this.formGroup.get('favoriteFood').patchValue(this.foodProvider.getDefaultFood());
  }

  openSelection() {
    this.select.open();
  }

  clearSelection(){
    this.select.value = null;
  }

  selectOption() {
     this.foods$.subscribe(options => {
       this.select.value = options[1];
     });
  }
}


service:
///////////////////////////////////////


import {Injectable} from '@angular/core';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Observable} from 'rxjs/Observable';
import {Food} from '../../domain-model/entities/food';

@Injectable()
export class FoodProvider{

  private readonly foodSubject:BehaviorSubject<Food[]> = new BehaviorSubject<Food[]>([

    new Food(  {value: 'steak-0', viewValue: 'Steak'}),
    new Food({value: 'pizza-1', viewValue: 'Pizza'}),
    new Food({value: 'tacos-2', viewValue: 'Tacos'})


  ]);

  getFood():Observable<Food[]>{
      return this.foodSubject.asObservable();
  }


  getDefaultFood():Food {

    var foods:Food[] = this.foodSubject.getValue();
    return foods[0];
  }
}



export class Food{
  value:string;
  viewValue:string;

  constructor(props:{value:string, viewValue:string}){
    this.value = props.value;
    this.viewValue = props.viewValue;
  }
}



I'm using the same object when selecting

I completely missed that part, sorry.

Aparently this is causing this behavior:

https://github.com/angular/material2/blob/915a2b732760eca047939acff7b79c00f7bc27e7/src/lib/select/select.ts#L394-L402

It looks like the ControlValueAcessor registered "onChange" function should have been called too:

if (newValue !== this._value) {
  this.writeValue(newValue);
  this._onChange(newValue);
  this._value = newValue;
}

Thanks for looking into it @julianobrasil, that is definitely the issue.

What is the status of pull request merging?

I have an issue where I cannot set the value of the <mat-select> when using in conjuction with a reactive form group, is this also related to this issue?

I'm having the same issue as @casizzi . "@angular/material": "^5.2.4"

@mluis if you set the value in the formGroup to use one of the mat-select-options then the view will update like this https://stackblitz.com/edit/angular-material2-issue-3eafh1?file=app/app.component.ts,

or you can simply use an ngModel on the select to kinda fake it with a new value like here https://stackoverflow.com/questions/50487942/set-select-input-value-via-angular-fromcontrol-value/50488087?noredirect=1#comment87989723_50488087

Awesome! Waiting hard for that fix to get released 馃憤

@mluis if you set the value in the formGroup to use one of the mat-select-options then the view will update like this https://stackblitz.com/edit/angular-material2-issue-3eafh1?file=app/app.component.ts,

or you can simply use an ngModel on the select to kinda fake it with a new value like here https://stackoverflow.com/questions/50487942/set-select-input-value-via-angular-fromcontrol-value/50488087?noredirect=1#comment87989723_50488087

Hello @casizzi,
When you use NgModel in conjunction of FormControl, Angular warn you :

    console.warn node_modules/@angular/forms/bundles/forms.umd.js:1544

          It looks like you're using ngModel on the same form field as formControlName.
          Support for using the ngModel input property and ngModelChange event with
          reactive form directives has been deprecated in Angular v6 and will be removed
          in Angular v7.

          For more information on this, see our API docs here:
          https://angular.io/api/forms/FormControlName#use-with-ngmodel

Is it planned to fix this issue ?
Regards.

Did anyone find a workaround to that issue?

There should be value attribute on the mat-option tag

<mat-option *ngFor="let item of items" [value]="item.name"> {{item.name}} </mat-option>

@rip222 's solution works:

<form [formGroup]="userForm" >
<mat-form-field>
          <mat-label>Role</mat-label>
          <mat-select formControlName="role">
            <mat-option *ngFor="let role of Object.values(UserRole)" [value]="role">
              {{ role }}
            </mat-option>
          </mat-select>
        </mat-form-field>
</form>

Any updates on this?
I got options defined by

visibilitySteps: Visibility[] = [
  { value: 0, viewValue: 'All' },
  { value: 2, viewValue: 'Some' },
  { value: 3, viewValue: 'One' }
];

A reactive form set by a formBuilder:

this.quoteForm = this.formBuilder.group({
  'visibility': [this.visibilitySteps[this.some_inital_index]],
});

And the group in HTML as:

<form [formGroup]="quoteForm" class="form">
  <mat-form-field class="form-element">
    <mat-select fromControlName="visibility">
      <mat-option *ngFor="let vis of visibilitySteps" value="vis">
        {{vis.viewValue}}
      </mat-option>
    </mat-select>
  </mat-form-field>
</form>

But the form does never display the given inital value and no matter what is choose, this.quoteForm.value will always hold the inital value.

EDIT: Fixed. Just missed the braces around [value] so the select form never had any actual value to set the form to.

I'm having the same issue as @Thomseeen . "@angular/material": "^8.2.3" . How will I to edit a form with mat-select?
i think it is a serious failure

@clima27 as I have stated above after edit, I have fixed the problem. It was a simple mistake on my side. Check the stackblitz example from above

@mluis if you set the value in the formGroup to use one of the mat-select-options then the view will update like this https://stackblitz.com/edit/angular-material2-issue-3eafh1?file=app/app.component.ts,

Was this page helpful?
0 / 5 - 0 ratings

Related issues

3mp3ri0r picture 3mp3ri0r  路  3Comments

xtianus79 picture xtianus79  路  3Comments

savaryt picture savaryt  路  3Comments

alanpurple picture alanpurple  路  3Comments

jelbourn picture jelbourn  路  3Comments