Angular Material: mat-select not selecting default
AngularTypescriptAngular Material2Angular Problem Overview
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
Angular Solutions
Solution 1 - Angular
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>
Solution 2 - Angular
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;
}
Solution 3 - Angular
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.
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>
Solution 4 - Angular
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>
Solution 5 - Angular
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;
}
Solution 6 - Angular
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.
Solution 7 - Angular
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();
});
}
Solution 8 - Angular
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.
Solution 9 - Angular
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}
Solution 10 - Angular
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
Solution 11 - Angular
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;
}
Solution 12 - Angular
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 []
Solution 13 - Angular
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.
Solution 14 - Angular
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>
Solution 15 - Angular
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>
Solution 16 - Angular
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.
Solution 17 - Angular
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.
Solution 18 - Angular
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>
Solution 19 - Angular
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>
Solution 20 - Angular
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