How to fix IE select issue when dynamically changing options

Internet ExplorerAngularjsHtml Select

Internet Explorer Problem Overview


I have a set of selects that all have the same options. Then I run those options through a filter so that any options that are selected in a different select don't show up in the select. See this jsFiddle (in non-IE browser) to see what I mean. Basically I'm preventing the same option from being selected multiple times among the selects

Now, what I've done has an issue in IE. Open that fiddle in IE (I've only tried it in IE9, but I'm guessing previous versions have the same issue). Change the last select to AAA. Notice how the 3 other selects all changed what they display. The model for them did not change, but IE somehow chokes when the options are changed.

My questions is first, am I doing something wrong with this functionality in general? This same code does exactly what I want in Chrome and FF, but am I doing something that I just shouldn't be? Second, how can I get around this in IE? I tried some timeouts that would clear and re-set the model, but things noticeable jumped around. I'm wondering if there's a good, clean, low impact workaround for this.

Any help would be much appreciated. Thanks.

--UPDATE--

This has been fixed in Angular itself with version 1.3.3 using A. S. Ranjan's solution below. See new fiddle with 1.3.3: http://jsfiddle.net/m2ytyapv/

//dummy code so I can post the edit

Internet Explorer Solutions


Solution 1 - Internet Explorer

I experienced the same issue the other night and after throwing everything I could think of at it I've come to the conclusion that IE just doesn't want to handle updating filters when using selects.

My solution is to change your selects to look like this:

 <select class="selectList" ng-repeat="currId in selectedIds" ng-model="selectedIds[$index]"  ng-options="currOption.id as currOption.value for currOption in myObj | myfilter:selectedIds:$index" data-ng-change="fixIE()"></select>

They now have a class and an ng-change on them. Then in your controller do this fun little bit of code:

$scope.fixIE = function(){
    //code to check if IE so the other browsers don't get this ugly hack.
    var selectLists = document.querySelectorAll(".selectList");
    for(var x = 0;x  < selectLists.length; x++){
        selectLists[x].parentNode.insertBefore(selectLists[x], selectLists[x]);
    }       
};

What it does is rip the elements out of the DOM and replace them into the same location. Here's a working fiddle jsFiddle

Some of the other solutions I tried that didn't involve javascript were things like toggling the display/visibility of the select. Having their zIndex's moved. The only thing that for sure fixed it was this piece of code.

Solution 2 - Internet Explorer

I have the fix.

We have to add and remove options list to trigger the rendering in IE8.

http://kkurni.blogspot.com.au/2013/10/angularjs-ng-option-with-ie8.html


/**
 * Fix for IE select menus getting stuck when their underlying list changes.
 * Original code: http://kkurni.blogspot.com.au/2013/10/angularjs-ng-option-with-ie8.html
 * 
 * Set the `ie-select-fix` attribute to the model expression that should trigger the list to re-render.
 * 
 * @example <select ng-model="modelValue" ie-select-fix="itemList" ng-options="item.label for item in itemList">
 */
app.directive('ieSelectFix', ['$document',
        function($document) {
 
            return {
                restrict: 'A',
                require: 'ngModel',
                link: function(scope, element, attributes, ngModelCtrl) {
                    var isIE = $document[0] && $document[0].attachEvent;
                    if (!isIE) return;
                    
                    var control = element[0];
                    //to fix IE8 issue with parent and detail controller, we need to depend on the parent controller
                    scope.$watch(attributes.ieSelectFix, function() {
                        // setTimeout is needed starting from angular 1.3+
                        setTimeout(function() {
                            //this will add and remove the options to trigger the rendering in IE8
                            var option = document.createElement("option");
                            control.add(option,null);
                            control.remove(control.options.length-1);
                        }, 0);
                    });
                }
            }
        }
    ]);

Solution 3 - Internet Explorer

I've finally come up with a solution that works for my needs. Basically what appears to be happening is that the text for the option at the selected index is pointing to the old string that used to be in that place. I believe changing this text updates the strings and/or references. I did something like this:

angular.forEach($("select"), function (currSelect) {
     currSelect.options[currSelect.selectedIndex].text += " ";
});

Here is the updated fiddle: http://jsfiddle.net/H48sP/35/

In my app, I have a directive where these selects are, and so I do element.find("select") instead of $("select") to limit the scope of the element selecting. The text is forced to refresh and so displays correctly after all the digest cycles run.

If you have run into this same issue, you may need to add a $timeout like in the fiddle, and/or you may need to later remove the extra space that was added to the option text if that becomes a problem.

Solution 4 - Internet Explorer

Adding couple of lines the following places (marked in bold as **) in render function of the selectDirective in angular.js worked fine for me. I am looking if there is any other possible solution other than patching angularJS or the forEach given below?

            if (existingOption.label !== option.label) {
              lastElement.text(existingOption.label = option.label);
              **lastElement.attr('label', existingOption.label);**
            }

and

              (element = optionTemplate.clone())
                  .val(option.id)
                  .attr('selected', option.selected)
                  .text(option.label);
              **element.attr('label', option.label);**

The issue was the label attribute of HTMLOptionElement is not the same as the text attribute if label is blank in IE.

This can be seen verified by adding the following code after the screen has loaded and looking at the web console of FF and IE to see the difference. If you uncomment the last line where the label is set to text it works fine. Alternatively patch angular.js as above.

// This is an IE fix for not updating the section of dropdowns which has ng-options with filters
angular.forEach($("select"), function (currSelect) {
	console.log("1.text ", currSelect.options[currSelect.selectedIndex].text);
	console.log("1.label ", currSelect.options[currSelect.selectedIndex].label);
	//console.log("1.innerHTML ", currSelect.options[currSelect.selectedIndex].innerHTML);
	//console.log("1.textContent ", currSelect.options[currSelect.selectedIndex].textContent);
	//console.log("1.cN.data ", currSelect.options[currSelect.selectedIndex].childNodes[0].data);
	//console.log("1.cN.nodeValue ", currSelect.options[currSelect.selectedIndex].childNodes[0].nodeValue);
	//console.log("1.cN.textContent ", currSelect.options[currSelect.selectedIndex].childNodes[0].textContent);
	//console.log("1.cN.wholeText ", currSelect.options[currSelect.selectedIndex].childNodes[0].wholeText);
	//console.log("1. ", currSelect.options[currSelect.selectedIndex], "\n");

	//currSelect.options[currSelect.selectedIndex].label = "xyz";
	//currSelect.options[currSelect.selectedIndex].label = currSelect.options[currSelect.selectedIndex].text;
});

Solution 5 - Internet Explorer

The problem seems to be related to the order of the options returned by the filter. When you change the last option to A, the other select options changes. What seems to cause a problem for IE is that the selected option changes place. In the first select box C is selected from the options in the following order: A, B, C, D. The selected option is the third option. When you change the forth select box from G to A, the filter changes the options in the first box to B, C, D, G. The selected option is now the second option, and this causes a problem with IE. This might be a bug in Angular, or it might be to some strange behavior in IE. I've created a fork that works around this by making sure that selected element is always the first option from the filtered options:

   var newOptions = [],selected;
    angular.forEach(allOptions, function (currentOption) {
        if (!isIdInUse(selectedIds, currentOption.id)){
            newOptions.push(currentOption);
        }else if(currentOption.id == selectedIds[parseInt(index)]){
            selected = currentOption;
        }
    });
    if(selected){newOptions.unshift(selected);}

http://jsfiddle.net/XhxSD/ (old)

Update:

I did some debugging and found the line that causes problems in IE, but I don't understand why. It does seem like a rendering bug or something. I've created another workaround that doesn't require any rearranging of the options - it's a directive that watches for changes on the select element. If a change is detected it appends an option and removes it immediately:

.directive('ieSelectFix',function($timeout){
  return {
    require:'select',
    link: function (scope, element) {
      var isIE = document.attachEvent;

      if(isIE){
        $timeout(function(){
          var index = element.prop('selectedIndex'), children = element.children().length;
          scope.$watch(function(){
            if(index !== element.prop('selectedIndex') || children !== element.children().length){
              index = element.prop('selectedIndex');
              children = element.children().length;
              var tmp =angular.element('<option></option>');
              element.append(tmp);
              tmp.remove();
            }
          })

        });
      }
    }
  }
});

Just add ie-select-fix to select elements inside ng-repeats:

<div ng-app="myApp" ng-controller="MyCtrl">
  <select ie-select-fix ng-repeat="currId in selectedIds" ng-model="selectedIds[$index]"  ng-options="currOption.id as currOption.value for currOption in myObj | myfilter:selectedIds:$index"></select><br>
  {{selectedIds}}
</div>

http://jsfiddle.net/VgpyZ/ (new)

Solution 6 - Internet Explorer

i've found the same bug in IE with select's.. this bug is the result of cloning DOM nodes..

if you instantiating a SELECT like (jQuery style):

$select = $template.clone();

and then doing:

$select.html('<option>111</option>');

you'll get the bug, described above..

BUT, if you instantiate

$select = $('< div />').html( $template ).html();

no bugs occurred :)

Solution 7 - Internet Explorer

Oh, I'm going to hell for the following advice...!

I tried these suggestions, but none worked for me.

I was actually using Angular to populate select controls with multiple options in each.

<select class="cssMultipleSelect" multiple="multiple" ...>

Sometimes, Angular would populate these controls, the new data would appear, but in IE, you couldn't scroll up and down to view all of the options.

But, if you hit F12, modified the width, and put it back to its original width, then IE would burst back into life again, and you could scroll up and down in the list of values.

So, my solution was to call this, a second or so after Angular had finished populating the controls:

function RefreshMultipleSelectControls()
{
	//  A dodgy fix to an IE11 issue.
	setTimeout(function () {
	    $(".cssMultipleSelect").width("");
	}, 1500);
	setTimeout(function () {
	    $(".cssMultipleSelect").width(298);
	}, 1600);
}

(I told you this was a dodgy fix..)

One other thing: remember that in IE11, navigator.appName will now return NETSCAPE (rather than MSIE or Microsoft Internet Explorer)... so be careful when you're testing if your code is running on IE, or on a decent browser.

You've been warned..!!

Solution 8 - Internet Explorer

Seems that ie9 have problem with the index. Taking the second example and change it to the following code it worked:

  var hwcalcModule = angular.module('ie9select', []);

  function AnimalCtrl($scope) {
    $scope.categories = [{
        name: "Cats",
        kinds: ["Lion", "Leopard", "Puma"]
    }, {
        name: "Dogs",
        kinds: ["Chihua-Hua", " Yorkshire Terrier", "Pitbull"]
    }];

  $scope.animals = [{
      category: $scope.categories[1],
      kind: $scope.categories[1].kinds[1]
  }];

  $scope.changeCategory = function (animal) {
      console.log(animal.category.name);
      var name = animal.category.name;
      var index = 0;
       angular.forEach($scope.categories, function (currentOption) {
           console.log(currentOption.name);
          if (name == currentOption.name)
          {
              console.log(index);
              $scope.animals = [{
                  category: $scope.categories[index],
                  kind: $scope.categories[index].kinds[0]
              }];
           }
           index++;
       });
  }
}

http://jsfiddle.net/seoservice/nFp62/10/

Solution 9 - Internet Explorer

On the lines of Mathew Berg answer, I modified it to work using AngularJS directive:

angular.module('select',[]).directive("select", function() {
    return {
      restrict: "E",
      require: "?ngModel",
      scope: false,
      link: function (scope, element, attrs, ngModel) {
        
        if (!ngModel) {
          return;
        }
        
        element.bind("change", function() {
            //Fix for IE9 where it is not able to properly handle dropdown value change
            //The fix is to rip out the dropdown from DOM and add it back at the same location
            if (isIE9){
                this.parentNode.insertBefore(this, this);   //this rips the elements out of the DOM and replace it into the same location.
            }
        })
      }
   }
});

This way the fix applies to all select elements in the project and you do not have to change any existing HTML markup. I also used the following method to detect IE version to set isIE9 variable to true:

var Browser = {
    IsIE: function () {
        return navigator.appVersion.indexOf("MSIE") != -1;
    },
    Navigator: navigator.appVersion,
    Version: function() {
        var version = 999; // we assume a sane browser
        if (navigator.appVersion.indexOf("MSIE") != -1)
            // bah, IE again, lets downgrade version number
            version = parseFloat(navigator.appVersion.split("MSIE")[1]);
        return version;
    }
};

var oldIE = false;      //Global Variable
var isIE9 = false;      //Global Variable
if (Browser.IsIE && Browser.Version() <= 8) {
    oldIE = true;
}

if (Browser.IsIE && Browser.Version() == 9) {
    isIE9 = true;
}

Solution 10 - Internet Explorer

I had to change scope.$watch to scope.$watchCollection to make @kkurni solution above to work for IE9. Just wanted to help others out who were still having issues in IE9 for rendering select options when they change.

Solution 11 - Internet Explorer

The rendering gets updated and synched if you change some attribute. An innocuous change may be to set the selectedIndex attribute to its own value:

function fixIEselect() {
    for (var nForm = 0; nForm < document.forms.length; ++nForm) {
        var form = document.forms[nForm];
        var children = form.children;
        for (var nChild = 0; nChild < children.length; ++nChild) {
            var child = children.item(nChild);
            if (child.tagName == "SELECT") {
                alert("Fixed: " + child.name);
                child.selectedIndex = child.selectedIndex; // dummy nop but not
            }
        }
    }
}

fixIEselect();

Solution 12 - Internet Explorer

There is less expensive way to enforce control re-rendering after dynamic options are added. So, instead of inserting/removing dummy element to the dropdown you can reset CSS styles which cause control rendering, e.g.

selElement.style.zoom = selElement.style.zoom ? "" : 1;

Solution 13 - Internet Explorer

I have a workaround for the IE picklist issue

Before Fix:http://plnkr.co/edit/NGwG1LUVk3ctGOsX15KI?p=preview

After Fix:http://plnkr.co/edit/a7CGJavo2m2Tc73VR28i?p=preview

$("select").click(function(){
  $(this).append('<option></option>');
   $(this).find('option:last').remove();

});

I just added dummy option for the dom to rerender the select and removed it. let me know it works for you

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questiondnc253View Question on Stackoverflow
Solution 1 - Internet ExplorerMathew BergView Answer on Stackoverflow
Solution 2 - Internet ExplorerkkurniView Answer on Stackoverflow
Solution 3 - Internet Explorerdnc253View Answer on Stackoverflow
Solution 4 - Internet ExplorerA. S. RanjanView Answer on Stackoverflow
Solution 5 - Internet ExplorerjoakimblView Answer on Stackoverflow
Solution 6 - Internet ExplorerenslavedView Answer on Stackoverflow
Solution 7 - Internet ExplorerMike GledhillView Answer on Stackoverflow
Solution 8 - Internet Explorerseoservice.chView Answer on Stackoverflow
Solution 9 - Internet ExplorerPawan PillaiView Answer on Stackoverflow
Solution 10 - Internet ExplorerbazzingaView Answer on Stackoverflow
Solution 11 - Internet ExplorerLuchosteinView Answer on Stackoverflow
Solution 12 - Internet ExplorerTavrietsView Answer on Stackoverflow
Solution 13 - Internet Explorerapurba mandalView Answer on Stackoverflow