[angular] Angular Material: mat-select not selecting default

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 it's 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

This question is related to angular typescript angular-material2

The answer is


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


I'm using Angular 5 and reactive forms with mat-select and couldn't get either of the above solutions to display the initial value.

I had to add [compareWith] to deal with the different types being used within the mat-select component. Internally, it appears mat-select uses an array to hold the selected value. This is likely to allow the same code to work with multiple selections if that mode is turned on.

Angular Select Control Doc

Here's my solution:

Form Builder to initialize the form control:

this.formGroup = this.fb.group({
    country: new FormControl([ this.myRecord.country.id ] ),
    ...
});

Then implement the compareWith function on your component:

compareIds(id1: any, id2: any): boolean {
    const a1 = determineId(id1);
    const a2 = determineId(id2);
    return a1 === a2;
}

Next create and export the determineId function (I had to create a standalone function so mat-select could use it):

export function determineId(id: any): string {
    if (id.constructor.name === 'array' && id.length > 0) {
       return '' + id[0];
    }
    return '' + id;
}

Finally add the compareWith attribute to your mat-select:

<mat-form-field hintLabel="select one">
<mat-select placeholder="Country" formControlName="country" 
    [compareWith]="compareIds">

    <mat-option>None</mat-option>
    <mat-option *ngFor="let country of countries" [value]="country.id">
                        {{ country.name }}
    </mat-option>
</mat-select>
</mat-form-field>

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.


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

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>

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.


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

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 []


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 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.


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

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

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>

_x000D_
_x000D_
public options2 = [_x000D_
  {"id": 1, "name": "a"},_x000D_
  {"id": 2, "name": "b"}_x000D_
]_x000D_
 _x000D_
YourFormGroup = FormGroup; _x000D_
mode: 'create' | 'update' = 'create';_x000D_
_x000D_
constructor(@Inject(MAT_DIALOG_DATA) private defaults: defautValuesCpnt,_x000D_
      private fb: FormBuilder,_x000D_
      private cd: ChangeDetectorRef) {_x000D_
}_x000D_
  _x000D_
ngOnInit() {_x000D_
_x000D_
  if (this.defaults) {_x000D_
    this.mode = 'update';_x000D_
  } else {_x000D_
    this.defaults = {} as Cpnt;_x000D_
  }_x000D_
_x000D_
  this.YourFormGroup.patchValue({_x000D_
    ..._x000D_
    fCtrlName: this.options2.find(x => x.name === this.defaults.name).id,_x000D_
    ... _x000D_
  });_x000D_
_x000D_
  this.YourFormGroup = this.fb.group({_x000D_
    fCtrlName: [ , Validators.required]_x000D_
  });_x000D_
_x000D_
}
_x000D_
  <div>_x000D_
    <mat-select formControlName="fCtrlName"> <mat-option_x000D_
          *ngFor="let option of options2"_x000D_
          value="{{ option.id }}">_x000D_
        {{ option.name }}_x000D_
      </mat-option>_x000D_
    </mat-select>_x000D_
  </div>
_x000D_
_x000D_
_x000D_


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.


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}


Examples related to angular

error NG6002: Appears in the NgModule.imports of AppModule, but could not be resolved to an NgModule class error TS1086: An accessor cannot be declared in an ambient context in Angular 9 TS1086: An accessor cannot be declared in ambient context @angular/material/index.d.ts' is not a module Why powershell does not run Angular commands? error: This is probably not a problem with npm. There is likely additional logging output above Angular @ViewChild() error: Expected 2 arguments, but got 1 Schema validation failed with the following errors: Data path ".builders['app-shell']" should have required property 'class' Access blocked by CORS policy: Response to preflight request doesn't pass access control check origin 'http://localhost:4200' has been blocked by CORS policy in Angular7

Examples related to typescript

TS1086: An accessor cannot be declared in ambient context Element implicitly has an 'any' type because expression of type 'string' can't be used to index Angular @ViewChild() error: Expected 2 arguments, but got 1 Typescript: No index signature with a parameter of type 'string' was found on type '{ "A": string; } Understanding esModuleInterop in tsconfig file How can I solve the error 'TS2532: Object is possibly 'undefined'? Typescript: Type 'string | undefined' is not assignable to type 'string' Typescript: Type X is missing the following properties from type Y length, pop, push, concat, and 26 more. [2740] Can't perform a React state update on an unmounted component TypeScript and React - children type?

Examples related to angular-material2

Angular-Material DateTime Picker Component? How to add icon to mat-icon-button 'mat-form-field' is not a known element - Angular 5 & Material2 Angular Material: mat-select not selecting default Checkbox angular material checked by default How to set the color of an icon in Angular Material? mat-form-field must contain a MatFormFieldControl Can't bind to 'formControl' since it isn't a known property of 'input' - Angular2 Material Autocomplete issue Angular2 Material Dialog css, dialog size No value accessor for form control with name: 'recipient'