Angular Material: mat-select not selecting default

Viewed 365729

I have a mat-select where the options are all objects defined in an array. I am trying to set the value to default to one of the options, however it is being left selected when the page renders.

My typescript file contains:

  public options2 = [
    {"id": 1, "name": "a"},
    {"id": 2, "name": "b"}
  ]
  public selected2 = this.options2[1].id;

My HTML file contains:

  <div>
    <mat-select
        [(value)]="selected2">
      <mat-option
          *ngFor="let option of options2"
          value="{{ option.id }}">
        {{ option.name }}
      </mat-option>
    </mat-select>
  </div>

I have tried setting selected2 and the value in mat-option to both the object and its id, and have tried using both [(value)] and [(ngModel)] in the mat-select, but none are working.

I am using material version 2.0.0-beta.10

19 Answers

Use a binding for the value in your template.

value="{{ option.id }}"

should be

[value]="option.id"

And in your selected value use ngModel instead of value.

<mat-select [(value)]="selected2">

should be

<mat-select [(ngModel)]="selected2">

Complete code:

<div>
  <mat-select [(ngModel)]="selected2">
    <mat-option *ngFor="let option of options2" [value]="option.id">{{ option.name }}</mat-option>
  </mat-select>
</div>

On a side note as of version 2.0.0-beta.12 the material select now accepts a mat-form-field element as the parent element so it is consistent with the other material input controls. Replace the div element with mat-form-field element after you upgrade.

<mat-form-field>
  <mat-select [(ngModel)]="selected2">
    <mat-option *ngFor="let option of options2" [value]="option.id">{{ option.name }}</mat-option>
  </mat-select>
</mat-form-field>

Use compareWith, A function to compare the option values with the selected values. see here: https://material.angular.io/components/select/api#MatSelect

For an object of the following structure:

listOfObjs = [{ name: 'john', id: '1'}, { name: 'jimmy', id: '2'},...]

Define markup like this:

<mat-form-field>
  <mat-select
    [compareWith]="compareObjects"
    [(ngModel)]="obj">
       <mat-option  *ngFor="let obj of listOfObjs" [value]="obj">
          {{ obj.name }}
       </mat-option>
    </mat-select>
</mat-form-field>

And define comparison function like this:

compareObjects(o1: any, o2: any): boolean {
  return o1.name === o2.name && o1.id === o2.id;
}

You should be binding it as [value] in the mat-option as below,

<mat-select placeholder="Panel color" [(value)]="selected2">
  <mat-option *ngFor="let option of options2" [value]="option.id">
    {{ option.name }}
  </mat-option>
</mat-select>

LIVE DEMO

You can simply implement your own compare function

[compareWith]="compareItems"

See as well the docu. So the complete code would look like:

  <div>
    <mat-select
        [(value)]="selected2" [compareWith]="compareItems">
      <mat-option
          *ngFor="let option of options2"
          value="{{ option.id }}">
        {{ option.name }}
      </mat-option>
    </mat-select>
  </div>

and in the Typescript file:

  compareItems(i1, i2) {
    return i1 && i2 && i1.id===i2.id;
  }

As already mentioned in Angular 6 using ngModel in reactive forms is deprecated (and removed in Angular 7), so I modified the template and the component as following.

The template:

<mat-form-field>
    <mat-select [formControl]="filter" multiple 
                [compareWith]="compareFn">
        <mat-option *ngFor="let v of values" [value]="v">{{v.label}}</mat-option>
    </mat-select>
</mat-form-field>

The main parts of the component (onChanges and other details are omitted):

interface SelectItem {
    label: string;
    value: any;
}

export class FilterComponent implements OnInit {
    filter = new FormControl();

    @Input
    selected: SelectItem[] = [];

    @Input()
    values: SelectItem[] = [];

    constructor() { }

    ngOnInit() {
        this.filter.setValue(this.selected);
    }

    compareFn(v1: SelectItem, v2: SelectItem): boolean {
        return compareFn(v1, v2);
    }
}

function compareFn(v1: SelectItem, v2: SelectItem): boolean {
    return v1 && v2 ? v1.value === v2.value : v1 === v2;
}

Note this.filter.setValue(this.selected) in ngOnInit above.

It seems to work in Angular 6.

I had problem in binding first option, when page load. Below has the solution which helped me

.html

<mat-form-field appearance="outline">    
    <mat-select #teamDropdown 
        [ngModel]="selectedGroupId" (selectionChange)="selectedGroupId=$event.value">
        <mat-option value=undefined>Please Select</mat-option>
        <mat-option *ngFor="let grp of groups" [value]="grp.groupsId">
            {{grp.groupName}}
        </mat-option>
    </mat-select>
</mat-form-field>

.ts

@ViewChild('teamDropdown') teamDropdown: MatSelect;
ngAfterViewInit() {
    setTimeout(() => {
        this.teamDropdown.options.first.select();
    });
}

I did it just like in these examples. Tried to set the value of the mat-select to the value of one of the mat-options. But failed.

My mistake was to do [(value)]="someNumberVariable" to a numeric type variable while the ones in mat-options were strings. Even if they looked the same in the template it would not select that option.

Once I parsed the someNumberVariable to a string everything was totally fine.

So it seems you need to have the mat-select and the mat-option values not only be the same number (if you are presenting numbers) but also let them be of type string.

The solution for me was:

<mat-form-field>
  <mat-select #monedaSelect  formControlName="monedaDebito" [attr.disabled]="isLoading" [placeholder]="monedaLabel | async ">
  <mat-option *ngFor="let moneda of monedasList" [value]="moneda.id">{{moneda.detalle}}</mat-option>
</mat-select>

TS:

@ViewChild('monedaSelect') public monedaSelect: MatSelect;
this.genericService.getOpciones().subscribe(res => {

  this.monedasList = res;
  this.monedaSelect._onChange(res[0].id);


});

Using object: {id: number, detalle: string}

Try this!

this.selectedObjectList = [{id:1}, {id:2}, {id:3}]
this.allObjectList = [{id:1}, {id:2}, {id:3}, {id:4}, {id:5}]
let newList = this.allObjectList.filter(e => this.selectedObjectList.find(a => e.id == a.id))
this.selectedObjectList = newList

My solution is little tricky and simpler.

<div>
    <mat-select
        [placeholder]="selected2">
      <mat-option
          *ngFor="let option of options2"
          value="{{ option.id }}">
        {{ option.name }}
      </mat-option>
    </mat-select>
  </div>

I just made use of the placeholder. The default color of material placeholder is light gray. To make it look like the option is selected, I just manipulated the CSS as follows:

::ng-deep .mat-select-placeholder {
    color: black;
}

Binding or setting of default value works only if the value attribute on MatSelect is comparable to value attribute binded to MatOption. If you bind caption of your item to value attribute of mat-option element you must set the default element on mat-select to caption of your item too. If you bind Id of your item to mat-option, you must bind id to mat-select too, not a whole item, caption or any other, only the same field.

But you need to do it with binding []

I did this.

<div>
    <mat-select [(ngModel)]="selected">
        <mat-option *ngFor="let option of options" 
            [value]="option.id === selected.id ? selected : option">
            {{ option.name }}
        </mat-option>
    </mat-select>
</div>

Normally you can do [value]="option", unless you get your options from some database?? I think either the delay of getting the data causes it not to work, or the objects gotten are different in some way even though they are the same?? Weirdly enough it's most likely the later one, as I also tried [value]="option === selected ? selected : option" and it didn't work.

I followed the above very carefully and still couldn't get the initial value selected.

The reason was that although my bound value was defined as a string in typescript, my backend API was returning a number.

Javascript loose typing simply changed the type at runtime (without error), which prevented selection the of the initial value.

Component

myBoundValue: string;

Template

<mat-select [(ngModel)]="myBoundValue">

Solution was to update the API to return a string value.

A very simple way to achieve this is using a formControl with a default value, inside a FormGroup (optional) for example. This is an example using an unit selector to an area input:

ts

H_AREA_UNIT = 1;
M_AREA_UNIT = 2;

exampleForm: FormGroup;

this.exampleForm = this.formBuilder.group({
  areaUnit: [this.H_AREA_UNIT],
});

html

<form [formGroup]="exampleForm">
 <mat-form-field>
   <mat-label>Unit</mat-label>
   <mat-select formControlName="areaUnit">
     <mat-option [value]="H_AREA_UNIT">h</mat-option>
     <mat-option [value]="M_AREA_UNIT">m</mat-option>
   </mat-select>
 </mat-form-field>
</form>

The only solution is that your form control or Ng Model value inside mat select tag must match the text assigned to value in option tags Here it is Ts file

selectedFood = 'Tacos';

Template

    <mat-form-field appearance="fill">
      <mat-label>Favorite Food</mat-label>
      <mat-select [(value)]="selectedFood">
        <mat-option value=''>---------</mat-option>
<mat-option value='Tacos'>Tacos</mat-option>
<mat-option value='Pizza'>Pizza</mat-option>
        
      </mat-select>
    </mat-form-field>
    <p>You selected: {{selectedFood}}</p>

A comparison between a number and a string use to be false, so, cast you selected value to a string within ngOnInit and it will work.

I had same issue, I filled the mat-select with an enum, using

Object.keys(MyAwesomeEnum).filter(k => !isNaN(Number(k)));

and I had the enum value I wanted to select...

I spent few hours struggling my mind trying to identify why it wasn't working. And I did it just after rendering all the variables being used in the mat-select, the keys collection and the selected... if you have ["0","1","2"] and you want to select 1 (which is a number) 1=="1" is false and because of that nothing is selected.

so, the solution is to cast you selected value to a string within ngOnInit and it will work.

TS

   optionsFG: FormGroup;
   this.optionsFG = this.fb.group({
       optionValue: [null, Validators.required]
   });

   this.optionsFG.get('optionValue').setValue(option[0]); //option is the arrayName

HTML

   <div class="text-right" [formGroup]="optionsFG">
     <mat-form-field>
         <mat-select placeholder="Category" formControlName="optionValue">
           <mat-option *ngFor="let option of options;let i =index" [value]="option">
            {{option.Value}}
          </mat-option>
        </mat-select>
      </mat-form-field>
  </div>

public options2 = [
  {"id": 1, "name": "a"},
  {"id": 2, "name": "b"}
]
 
YourFormGroup = FormGroup; 
mode: 'create' | 'update' = 'create';

constructor(@Inject(MAT_DIALOG_DATA) private defaults: defautValuesCpnt,
      private fb: FormBuilder,
      private cd: ChangeDetectorRef) {
}
  
ngOnInit() {

  if (this.defaults) {
    this.mode = 'update';
  } else {
    this.defaults = {} as Cpnt;
  }

  this.YourFormGroup.patchValue({
    ...
    fCtrlName: this.options2.find(x => x.name === this.defaults.name).id,
    ... 
  });

  this.YourFormGroup = this.fb.group({
    fCtrlName: [ , Validators.required]
  });

}
  <div>
    <mat-select formControlName="fCtrlName"> <mat-option
          *ngFor="let option of options2"
          value="{{ option.id }}">
        {{ option.name }}
      </mat-option>
    </mat-select>
  </div>

Related