Components: mat-select - Allow mat-option selected property to be settable

Created on 22 Sep 2017  路  22Comments  路  Source: angular/components

Bug, feature request, or proposal:

Feature request

What is the expected behavior?

To be able to set the mat-option selected property

What is the current behavior?

Currently the mat-option selected property is readonly and you have to programatically select options.

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

Being able to bind to the selected property would be a cleaner implementation for a user versus having to do it manually, especially in cases where using cascading mat-select with multiple=true (harder to manage)

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

Angular 4.4.3
Material 2.0.0-beta.11
Windows 10
TypeScript - 2.5.2
All browsers

P3 materiaselect feature needs discussion

Most helpful comment

@Jonibigoud it's literally just that. Have a look at the offical Angular docs here.

In Material2 demo-app they have an example of the function with two implementations. It's here.

In my component I have a collection of User objects [people] for the options of mat select. The component receives a collection of selected User objects [users] as Input from previous state. Fair enough, objects in [people] and objects in [users] have different identities and the subset in the multiple select does not initialize with selected checkboxes by default.

So, the magical compareWith just literally compares objects by some given values and returns true or false, and the checkboxes on the subset of [people] get the status of selected. In my code I decided to go with [(ngModel]) binding:

    <mat-form-field>
        <mat-select [compareWith]="compareFn" name="users" [(ngModel)]="users" multiple>
            <mat-option *ngFor="let person of people" [value]="person">
               {{ person.username }}
            </mat-option>
       </mat-select>
    </mat-form-field>

And in the .ts file I utilize the function from the Angular doc to return true if two User objects have the same id:

    compareFn(user1: User, user2: User) {
        return user1 && user2 ? user1.id === user2.id : user1 === user2;
    }

If you have a similar use-case, it might work out-of-the-box.

On the what's-under-the-hood note compareWith got me going and after a little digging I found out that it is based on a function in Angular2 called looseIdentical (have a look here in your free time), which in turn derives from the identical in Dart.js library by Google. It can be found here .

All 22 comments

@djarekg How did you do for options programmatically selection?

If i use select() of MatOptions no selection occurred. If i do this in setTimeout() the option is correctly selected but mat-select has empty text for selected options.

The MatOption has a method, _selectViaInteraction, that will select that option passing true as user input, which will update the UI as well.

The method is not exposed in the d.ts, but if you call option['_selectViaInteraction'](), it will work.

@djarekg Thank you alot! It works!

For me it's giving error:
let optionArr : MatOption[]; optionArr['_selectViaInteraction'](); // error
// error from console :
._selectViaInteraction is not a function

@djarekg can you please make a plunker for it?

@imchaitanya9 You must call MatOption method ['_selectViaInteraction'] on single MatOption not on array.

example
optionArr.forEach((x) => { x['_selectViaInteraction'](); });

I achieved dynamically selected multiple options using ngModel:

<mat-select  class="full-width" #mulLoc required placeholder="Locations" multiple [(ngModel)]="resultLocation"   name="resultLocation">
<mat-option *ngFor="let l of locations; let i = index" class="multiple-field-box" [value]="l">
              {{ l.value }}
            </mat-option>
          </mat-select>

where

locations = [
{
id : 'USA',
value : 'united states'
},
{
id : 'IND',
value : 'india'
},
{
id : 'INS',
value : 'indonesia'
}
]
resultLocation = [locations[1], locations[2]]

Hope it should help!

@matte00 another approach is to use the select()/deselect() public methods available in MatOptions.

example:
optionArr.forEach(x => x.select());

I had to set the value parameter after content checked to avoid changed after checked errors. Using select() in the same way didn't work for me, and this updated my label to list checked items as well.

  @ViewChild(MatSelect) groupsSelect: MatSelect;
  ...

  ngAfterContentChecked() {
    // checks state for selected items i.e. isSelected
    // checks the boxes of the selected items in material dropdown by setting its value to an array of the values else an empty array
    const selected = this.state.items.length > 0
      ? this.state.items.filter((val: RefinementListItem) => val.isSelected).map((option: RefinementListItem) => option.value )
      : [];
    this.groupsSelect.value = selected
  }
<mat-form-field>
  <mat-select [placeholder]="title" multiple="true" >
    <mat-option 
      *ngFor="let item of items" 
      [value]="item.value"     
      (click)="refine($event, item)">
      {{ item.label | titlecase }}
    </mat-option>
  </mat-select>
</mat-form-field>

I could very well be missing the point of this issue report, but it seems that with plain old html options (and angular), one can do something like the following:

<select id="mySelector" formControlName="selectedOption">
  <option *ngFor="let optionVal of selectorOptions" [selected]="isSelected(optionVal,partiallyPrefilledFromAccountInfo.value.selectedOption)">{{optionVal}}</option>
</select>

The idea being we have a form that we want to pre-fill based on previously gathered information (to improve the user's experience by shortening their time filling in form fields).

For now, I will try the _selectViaInteraction solution.

(Again, it's possible I'm missing the point of this thread, I would be happy to post this elsewhere, but the OP seemed close to how I would describe my situation 馃槃 )

Depending on a use-case it is good to know that initializing some default options as selected might not work by simply binding to the ngModel, because default values are different object instances than those in the options array.
Thanks to the support for compareWith it is possible to set them as selected like so:

[html]
<mat-select [compareWith]="compareFn" ......>

[ts]
compareFn(c1: Object, c2: Object): boolean {
    return c1 && c2 ? c1.id === c2.id : c1 === c2;
}

In my case none of the above suggested solutions worked, but once I compared the default selected objects, that come from the parent component, with the objects in the mat-options array, the default options got initialized as selected automatically.

@akolybelnikov, would you mind sharing more code on this?
I can't figure out how to implement this "compareWith" function...
Thanks a lot.

@Jonibigoud it's literally just that. Have a look at the offical Angular docs here.

In Material2 demo-app they have an example of the function with two implementations. It's here.

In my component I have a collection of User objects [people] for the options of mat select. The component receives a collection of selected User objects [users] as Input from previous state. Fair enough, objects in [people] and objects in [users] have different identities and the subset in the multiple select does not initialize with selected checkboxes by default.

So, the magical compareWith just literally compares objects by some given values and returns true or false, and the checkboxes on the subset of [people] get the status of selected. In my code I decided to go with [(ngModel]) binding:

    <mat-form-field>
        <mat-select [compareWith]="compareFn" name="users" [(ngModel)]="users" multiple>
            <mat-option *ngFor="let person of people" [value]="person">
               {{ person.username }}
            </mat-option>
       </mat-select>
    </mat-form-field>

And in the .ts file I utilize the function from the Angular doc to return true if two User objects have the same id:

    compareFn(user1: User, user2: User) {
        return user1 && user2 ? user1.id === user2.id : user1 === user2;
    }

If you have a similar use-case, it might work out-of-the-box.

On the what's-under-the-hood note compareWith got me going and after a little digging I found out that it is based on a function in Angular2 called looseIdentical (have a look here in your free time), which in turn derives from the identical in Dart.js library by Google. It can be found here .

Thank you @akolybelnikov. I understand it now .
I had a similar use-case, now it works like a charm!

I followed @akolybelnikov 's directions and it worked perfectly.

For some weird reason I can only get the first option checked. I made a compareWith function that always returns true.

@akolybelnikov I want to set Mat-Select value from code. And based on assigned drop down value I need to show hide few form fields. I am setting the Mat-Select value from .ts file on initialization. like this
this.myForm.controls.myControl.setValue(this.dd[1]);
This is setting the value in Mat-Select drop down but it is not triggering selectionChange event and hence not able to show/hide other fields. Also reactive form mat-select field is not dirty.
Please suggest if there is a way to achieve this.

@SWGeekPD have you tried, if a dispatchEvent would do that in your case? It's not bound to Angular, but it should work.

Try this.myForm.controls.myControl.updateValueAndValidity() after setting.

Hello there! I know there were many solutions proposed here and throughout a long, long process of trying to determine how to pre-select your options I came up with a solution that worked for me. My problem was that I couldn't preset values as selected. The way I ended up fixing this was by adding in the mat-select. Once I did this any values in "theSelected" were populated as selected when initialization was ran, as long as the array of options had matching options/values. Therefore filter a list to contain your preselected values and plug it into "theSelected" and this should do the job. I know this answer is pretty similar to another answer so let me know if you have any questions.

This is what I'm doing:

HTML

<mat-form-field>
            <mat-select #thisselect placeholder="Brands" formControlName="brands"
                (selectionChange)="selectChanged($event)" multiple required>
                <mat-option *ngFor="let brand of brandList; index as i" [value]="i">{{brand.name}}</mat-option>
            </mat-select>
        </mat-form-field>

Typescript

@ViewChild('thisselect', {static: false}) thisselect: MatSelect;

ngAfterViewInit() {
    setTimeout(() => {
     // because the "[value]=" in the html is set to i (index)
     // we can set which items we want selected simply by using the index
      this.thisselect.value = [0, 2];
    }, 1);
  }

This is what I'm doing:

HTML

<mat-form-field>
            <mat-select #thisselect placeholder="Brands" formControlName="brands"
                (selectionChange)="selectChanged($event)" multiple required>
                <mat-option *ngFor="let brand of brandList; index as i" [value]="i">{{brand.name}}</mat-option>
            </mat-select>
        </mat-form-field>

Typescript

@ViewChild('thisselect', {static: false}) thisselect: MatSelect;

ngAfterViewInit() {
    setTimeout(() => {
     // because the "[value]=" in the html is set to i (index)
     // we can set which items we want selected simply by using the index
      this.thisselect.value = [0, 2];
    }, 1);
  }

Hi @helzgate,
Is there a way that instead of the index we can select them by the item's value? The index is repeated for each group iteration I believe. So all the groups start from 0, 1, 2 ... It cannot select other groups' items by index.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

RoxKilly picture RoxKilly  路  3Comments

3mp3ri0r picture 3mp3ri0r  路  3Comments

alanpurple picture alanpurple  路  3Comments

kara picture kara  路  3Comments

Miiekeee picture Miiekeee  路  3Comments