66

I've created the following example so you can see exactly what is happening: http://jsfiddle.net/8t2Ln/101/

The same thing happens if I use ng-options. I have a different reason for doing it this way, but for the simplification of the example left that part out.

As you can see it has by default two options. I'm displaying the selected value of the ng-model next to the select so you can see what it is. When you use the top piece to add a third option it sets the value to the value of that new option as is evidenced by the displayed ng-model value next to the select, but the select itself doesn't change to show the correct value selected.

Below is the sample code at the link:

var testApp = angular.module('testApp', ['ngRoute']);

testApp.controller('Ctrl', function ($scope) {

    $scope.newInput = '';
    $scope.inputDevice = [
        {
            value: '1',
            label: 'input1'
        },
        {
            value: '2',
            label: 'input2'
        }
    ];
    $scope.selectedDevice = '';

    $scope.addType = function () {
        var newElem = {
            label: $scope.newInput,
            value: '3'
        };
        $scope.inputDevice.push(newElem);
        $scope.selectedDevice = newElem.value;
    };


});

And here is the html:

<div ng-app="testApp">
    <div ng-controller="Ctrl">
        <p>
            <input type="text" ng-model="newInput" />
            <br />
            <button ng-click="addType()">Add Type</button>
        </p>
        <select ng-model="selectedDevice">
            <option></option>
            <option ng-repeat="i in inputDevice" value="{{ i.value }}">{{ i.label }} - {{ i.value }}</option>
        </select>
        {{ selectedDevice }}</div>
</div>
Jhorra
  • 6,233
  • 21
  • 69
  • 123

5 Answers5

99

This is exactly why you should not use ngRepeat to render select options. You should use ngOptions instead:

<select ng-model="selectedDevice" 
        ng-options="i.value as (i.label + '-' + i.value) for i in inputDevice">
    <option></option>
</select>

In general, avoid using ngRepeat for rendering select options. There are at least two good reasons. ngRepeat creates separate child scope per iteration, which is not needed in case of option tag. Another important caveat is that with ngRepeat you can only bind select to primitives like strings, but you won't be able to write object to ngModel with it.

Here is a demo below.

angular.module('demo', []).controller('DemoController', function($scope) {

    $scope.newInput = '';
    $scope.inputDevice = [
     {value: '1', label: 'input1'}, 
        {value: '2', label: 'input2'}
    ];
    
    $scope.selectedDevice = '';
    $scope.addType = function() {
        var newElem = {
            label: $scope.newInput,
            value: Number($scope.inputDevice[$scope.inputDevice.length - 1].value) + 1
        };
        $scope.inputDevice.push(newElem);
        $scope.selectedDevice = newElem.value;
        $scope.newInput = '';
    };

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js"></script>
<div ng-app="demo" ng-controller="DemoController">
    <form ng-submit="addType()">
        <input type="text" ng-model="newInput" />
        <button type="submit">Add Type</button>
    </form>
    <select ng-model="selectedDevice" ng-options="i.value as (i.label + ' - ' + i.value) for i in inputDevice">
        <option>Select</option>
    </select>
    {{ selectedDevice }}
</div>
dfsq
  • 191,768
  • 25
  • 236
  • 258
  • Fair enough. I thought I had tried it both ways, but apparently not. – Jhorra Oct 06 '14 at 07:41
  • 1
    @dfsq Perfect answer. Upvoting! I just thought Jhorra didn't want to use `ng-options` – JME Oct 06 '14 at 08:06
  • If I use `ng-options` I'm getting an empty item on top. For that reason I don't use ng-otpions in some cases. – Fabic Apr 17 '15 at 13:45
  • 2
    @BMS Empty item means that you don't set ngModel properly, so Angular doesn't know what option to select. – dfsq Apr 17 '15 at 14:04
  • To clarify, to not have a blank first item, set the value of the ng-model variable in your controller, or have ng-if render it on the dom only after that is set, if value is obtained async. – Bon Aug 03 '15 at 13:29
  • should use track by in the above expression for it to work – Souvik Basu Jun 25 '16 at 00:13
  • For me `option.name for option in data.availableOptions track by option.id` was not working. But the answer is working! – Alex78191 Jun 17 '17 at 01:02
9

The problem is that, since you're not using ng-options the browser had not finished rendering at the point when you set the new selectedDevice. If you're set on on using ng-options you can use this workaround. Use $timeout to wrap your $scope.selectedDevice = newElem.value; to ensure it runs after the browser has finished rendering the changes with ng-repeat.

I also added code to increment the next value on successive adds because hardcoding it to '3' meant that the third option would continually be selected even when more are added.

var testApp = angular.module('testApp', ['ngRoute']);

testApp.controller('Ctrl', function($scope, $timeout) {

  $scope.newInput = '';
  $scope.inputDevice = [{
    value: '1',
    label: 'input1'
  }, {
    value: '2',
    label: 'input2'
  }];
  $scope.selectedDevice = '';

  $scope.addType = function() {
    var last = Number($scope.inputDevice[$scope.inputDevice.length - 1].value) + 1;
    var newElem = {
      label: $scope.newInput,
      value: last.toString()
    };
    $scope.inputDevice.push(newElem);
    $timeout(function() {
      $scope.selectedDevice = newElem.value;
    }, 0);
  };

});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular-route.js"></script>

<div ng-app="testApp">
  <div ng-controller="Ctrl">
    <p>
      <input type="text" ng-model="newInput" />
      <br />
      <button ng-click="addType()">Add Type</button>
    </p>
    <select ng-model="selectedDevice">
      <option></option>
      <option ng-repeat="i in inputDevice" value="{{ i.value }}" ng-selelected="{{ selectedDevice == i.value }}">{{ i.label }} - {{ i.value }}</option>
    </select>
    {{ selectedDevice }}
  </div>
</div>
JME
  • 3,592
  • 17
  • 24
  • I worked out a solution using `ng-repeat` because I guess I misunderstood that you wanted to use it vs `ng-options`. At least you have an alternate way of doing it and an understanding why you original code didn't work as expected. – JME Oct 06 '14 at 08:06
4

I had a similar problem and the reason was that my key was a number, but when trying to set another value I was sending a string. The workaround in this case is to force to set the model value to the same type as the key.

eg:

<select ng-model="model" ng-options="option.{{ key }} as option.{{ label }} for option in options">
    <option value="">{{ emptyLabel }}</option>
</select>
if (scope.options.length > 0) {
    scope.keyType = typeof(scope.options[0][scope.key]);
}

...

if (scope.keyType == 'number') {
    scope.model = parseInt(newVal, 10);
} else {
    scope.model = newVal;
} 
Romain
  • 1,203
  • 2
  • 9
  • 10
1

I had the same issue of select not updating when ng-model is updated. I was retrieving the value for ng-model from a function which extracted the object from the array based on a key,value pair.

In doing this, the returned object had the hashkey property $$hashKey: "object:115"

The problem occurred when I created a copy of the object using angular.copy which stripped off this 'hashkey' property and hence would not get selected.

After I reorganized the code, to get ng-model value after angular.copy, the issue was resolved

ConstructorReviewers: function (oItem) {
      this.PERSON_ID = oItem.PERSON_ID;
      this.CHAIR_PERSON = oItem.CHAIR_PERSON;

      /* // Commented this part and added it to EditItem function      
      this.oDepartment = CommonFactory.FindItemInArray(vm.oCommittee.arrDepartments, 'NAME', this.DEPARTMENT, 'item');
      */    
      this.EditItem = function () {
           vm.EditItemOriginal = this;
           angular.copy(this, vm.EditItem); // After this update the ng-model into vm.EditItem.oTitle object
           vm.EditItem.oTitle = CommonFactory.FindItemInArray(vm.oCommittee.arrTitles, 'TITLE', vm.EditItem.TITLE, 'item');
           vm.Popup.ShowPopup(true, 'new_edit', Constants.Mentoring.Popup.Edit);
      }
}
Mahesh
  • 3,727
  • 1
  • 39
  • 49
1

I had the same problem. What solved it for me is converting the number to a string. Example:

$scope.selectedDevice = "" + newElem.value; 
Malek Hijazi
  • 4,112
  • 1
  • 26
  • 31