Material: md-select showing multiple selections after updating option list

Created on 7 Nov 2016  路  9Comments  路  Source: angular/material

Actual Behavior:

  • What is the issue? *md-select showing multiple selections after updating option list
  • What is the expected behavior? The md-select should have only one selection

CodePen (or steps to reproduce the issue): *

  • CodePen Demo which shows your issue: I have a codepen which attempts to reproduce this but I have not been able to at this time.
  • Details:
    I haven't been able to reproduce this in codepen, however I've reproduced my code pretty faithfully. As you can see in the screencap, a modal dialog pops up to allow the user to add an entry to the select list. The dialog shares the same scope as the calling controller. After an http post is made to add the item to the list, the dialog is closed and a method is called to refresh the list items (another http call). The select list is inside an ng-repeat which is inside an md-tab. After updating, the md-select shows the same item selected multiple times. Clicking on the whitespace of the md-side-nav or clicking on the tab title appears to force the md-select into the proper state. I have the list data and the selected item data shown for troubleshooting purposes. The selected item data does not show multiple selected items and the list data does not show duplicate items either. Also, opening the md-select list will revert it back to the proper state (not shown). My guess is that somehow, causing another digest is forcing it to correct itself. What could be causing this?
    screen_cap

Angular Versions: *

  • Angular Version: 1.5.8
  • Angular Material Version: 1.1.1

Additional Information:
Verified in IE 11 and Chrome 54.0.2840.59.

Troubleshooting Steps
Tracing the code in angular-material.js I stepped into the SelectMenuController's selectedLabels function and noted that selectedOptionEls.length would be 2 when this happened. I don't see how it could be 2, as the list is cleared and set fresh in my call to refresh the list.

vm.addCertificationType = function () {
    CertificationTypeService.add(vm.newCertificationType).then(function (response) {
        vm.newCertificationType = {};
        getCertificationTypes();
        $mdDialog.hide();
    });
};

function getCertificationTypes() {
    CertificationTypeService.getAll().then(function (response) {
        if (vm.certificationTypes)
            delete vm.certificationTypes;

        vm.certificationTypes = response.data;
    });
}

I inspect the DOM and there are no duplicate options when this is rendered, but at some point along the way, perhaps new items are being added to the DOM before the original ones are removed. I checked the network activity and there is only one call to retrieve the data. Just for the heck of it, I made two calls to getCertificationTypes and it showed 3 selected values but there were no dups in the list. Could this be some sort of priority issue where the ng-repeat must update the DOM before the selectedLabels() checks for options?
I've created a question on stackoverflow for this as well.

fixed

Most helpful comment

This works for me, just added track by $index
<md-option ng-repeat="option in vm.options track by $index" ng-value="option.value">

All 9 comments

I've added a second call with $timeout for both calls in an effort to troubleshoot and an additional duplicate shows up in the selected value labels:

        vm.addCertificationType = function () {
            CertificationTypeService.add(vm.newCertificationType).then(function (response) {
                vm.newCertificationType = {};
                $timeout(getCertificationTypes, 1000);
                $timeout(getCertificationTypes, 1000);
                //getCertificationTypes();
                $mdDialog.hide();
            });
        };

screen_cap2

For one of the selects, I'm setting the selected value manually to the first object in the list. If I force the selection to happen in the next digest, it will resolve the issue for that md-select but the related md-selects are unpredictable.

function getCertificationTypes() {
    CertificationTypeService.getAll().then(function (response) {
        if (vm.certificationTypes)
            delete vm.certificationTypes;

        vm.certificationTypes = response.data;

        vm.newSupplierCertification = {
            certificationTypeId: vm.certificationTypes[0].id,
            tempId: RandomService.guid()
        };

        var tmpNewSupplier = vm.newSupplierCertification;

        vm.newSupplierCertification = {};
        $timeout(function () {
            vm.newSupplierCertification = tmpNewSupplier;
        })
    });
}

screen_cap5

As you can see, the select label for all of them updates appropriately the first add but only the one where I am manually setting the select to the first item in the $timeout call above is updating appropriately for the successive calls.

If I manually add the item to the list and then sort the list afterwards then it works as expected, but this forces me to sort on the client. I believe it's acceptable to want to be able to replace the binding with an updated list without the above issue happening.

vm.addCertificationType = function () {
    CertificationTypeService.add(vm.newCertificationType).then(function (response) {
        vm.newCertificationType = {};

        vm.certificationTypes.push(response.data);
        vm.certificationTypes.sort(function (a, b) {
            return a.name.localeCompare(b.name);
        });

        vm.newSupplierCertification = {
            certificationTypeId: vm.certificationTypes[0].id,
            tempId: RandomService.guid()
        };
        $mdDialog.hide();
    });
};

Alternatively, if I add a track by to my md-option's ng-repeat then it works as well assumedly because there will never be duplicate items at any time in the DOM which would happen if completely replacing the list reference. My question is: why is the function to update the selected label being called before the duplicates are removed from the DOM?

<md-option ng-repeat="certificationType in addEditSupplierCtrl.certificationTypes track by certificationType.id" value="{{ certificationType.id }}">

screen_cap4

Maybe the same as #9442?

This works for me, just added track by $index
<md-option ng-repeat="option in vm.options track by $index" ng-value="option.value">

@juanarbol didn't worked for me. I tracked by $index and by my custom id...same issue.

Any news about this one?

@nachogarrone Have you tried sort your custom ids before do ng-repeat?

I used "ng-if" if the list not null or empty. Then before binding data, set the list to null first, and assign the new list to object.
This may not be the best result, because it redraw the element (destroy and draw), but it works.

<md-select ng-if="theList!=null" > <md-option ng-repeat="x in theList" value="x.id"> {{x.label}}</md-option> </md-select>

Nothing of the above helped, tracking by $index, customId, ng-if...
Solution was to add ng-selected to md-option and check if it has the same value with ng-model.

```
ng-if="formData.availableUnits !== null"
ng-model="formData.unit"
required>
label="{{key}}"
ng-repeat="(key, group) in formData.availableUnits">
ng-selected="formData.unit === item"
ng-value="item"
ng-repeat="item in group">{{item}}

This seems to have been fixed (via https://github.com/angular/material/pull/9695) in 1.1.2.

There are some details on StackOverflow about how to work around this for earlier versions.

Was this page helpful?
0 / 5 - 0 ratings