21

I have a problem with my ngModel in select not showing as selected. Both id and name are matching but its not working, see selectedState. Pointing model to the actual object within options array works, see selelectedState2. No idea whats going on ...

Fiddle: http://jsfiddle.net/fedorsmirnoff/b49n4Ldp/2/

<select ng-model="selectedState" ng-options="state.name for state in stateOptions"></select>

<select ng-model="selectedState2" ng-options="state.name for state in stateOptions"></select>

function MainCtrl($scope) {
 $scope.stateOptions = [
     {id: 1, name: "Alaska"},
     {id: 2, name: "Montana"},
     {id: 3, name: "Nebraska"},
     {id: 4, name: "Texas"}
  ]

 $scope.selectedState = {id: 2, name: "Montana"};

 $scope.selectedState2 = $scope.stateOptions[1];

}
PSL
  • 123,204
  • 21
  • 253
  • 243
Micor
  • 1,502
  • 4
  • 20
  • 39

6 Answers6

32

This is because each object has it's own $hashKey provided by Angular that Angular uses to determine whether they are the same. You're creating a new object (with a different $hashKey) on $scope.selectedState. The way you set it on $scope.selectedState2 is correct.

You can also use track by to make Angular track by state.id instead of the object's $hashKey:

<select ng-model="selectedState" ng-options="state.name for state in stateOptions track by state.id"></select>
m59
  • 43,214
  • 14
  • 119
  • 136
8

If you are providing an object as the model which does not hold the reference to the existing list, then use track by with the unique value of your model, so that instead of using the custom unique $$hashKey, ng-options will use the property that you provide in the track by for tracking the ng-model that is being set.

  ng-options="state.name for state in stateOptions track by state.id"

Demo

Not only that it is useful in setting ng-model to any reference, but also it has a great deal of performance effectiveness as well especially when your list gets refreshed, the elements will not be removed and recreated, instead angular will just update the existing element.

Here is a very good example for this.

Community
  • 1
  • 1
PSL
  • 123,204
  • 21
  • 253
  • 243
3

Angular Team stated this issue in the documentation for ngSelect here:

Note: ngModel compares by reference, not value. This is important when binding to an array of objects. See an example in this jsfiddle.

 $scope.options = [
    { label: 'one', value: 1 },
    { label: 'two', value: 2 }
  ];

  // Although this object has the same properties as the one in $scope.options,
  // Angular considers them different because it compares based on reference
  $scope.incorrectlySelected = { label: 'two', value: 2 };

  // Here we are referencing the same object, so Angular inits the select box correctly
  $scope.correctlySelected = $scope.options[1];
Dalorzo
  • 19,834
  • 7
  • 55
  • 102
  • This is not completely correct as per how ng-option works, you can always track by something and set the model have that property value. Other way would be to use `value as name for item in ..` syntax. You cannot always know which index selected items belongs to in an array (unless you loop through to get a match which is pretty inefficient) – PSL Sep 07 '14 at 01:45
  • @PSL this is based on Angular Team documentation if it is incomplete Angular Team also made the same mistake. – Dalorzo Sep 07 '14 at 03:05
2

When you set $scope.selectedState, you are actually creating a new javascript object, which is not an element of the $scope.stateOptions. Hence, the select element would not be selecting the corresponding element from $scope.stateOptions.

You can use 'track by' in the select expression if you need to select items by a unique attr value.

Tharaka
  • 2,355
  • 1
  • 20
  • 22
1

Try adding Track by state.id at the end of your ng-options statement.

Mindastic
  • 4,023
  • 3
  • 19
  • 20
0

I think Angular uses reference checking instead of comparing two objects with the same properties. In your case $scope.selectedState2 returns a different object. I usually use understore to find the selected item from an array for initialization.