简体   繁体   中英

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

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

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>

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

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.selectTeamDropdown.options.first.select();
    });
}

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.

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.

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>

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>

It is a good practice to have a compareWith function for comparing your objects when using angular material select. If you want more information on how to use mat-select you can check out the following link: The importance of the compare function in angular material select

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM