Can't set selected value of ng-options
Asked Answered
M

4

15

I am trying to populate a drop-down select options list and set a default selected value using ng-model and ng-options.

I have the following code in my view:

<select ng-model="thisTour.site" ng-options="site.name for site in siteList"></select>

And in my controller:

    $scope.siteList = [
        { id: 1, name: 'cycling'},
        { id: 2, name: 'walking'},
        { id: 3, name: 'holidays'}
    ]

    $scope.thisTour.site = { id: 2, name: 'walking'};

The list is getting populated with the correct 3 options from the siteList object, but it is not selecting walking by default as I would expect? Why not?

Now, when I change this:

$scope.thisTour.site = { id: 2, name: 'walking'};

To this:

$scope.thisTour.site = $scope.siteList[1];

Now it works. Why? Isn't it the same thing?

Mudfish answered 16/12, 2014 at 21:31 Comment(0)
S
46

That is because angular looks for object equality to bind it with your syntax and inyour case $scope.siteList[1] is not equal to { id: 2, name: 'walking'}; (2 objects are equal only if they point to the same reference). You can get around this in many ways, one easy way is to use track by syntax with ng-options to specify track by id, which will enable ng-option's options to be tracked by the specified property of the bound object rather than the object reference itself.

<select ng-model="thisTour.site" 
    ng-options="site.name for site in siteList track by site.id"></select>

You could also use the syntax to minimally set the ng-model to specify only the id using select as part in the syntax:-

Example:-

ng-options="site.id as site.name for site in siteList"

and model would just be:-

 $scope.thisTour.site = 2;

angular.module('app', []).controller('ctrl', function($scope){
  $scope.thisTour = {};
 $scope.siteList = [
        { id: 1, name: 'cycling'},
        { id: 2, name: 'walking'},
        { id: 3, name: 'holidays'}
    ]

    $scope.thisTour.site = { id: 2, name: 'walking'};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
  <select ng-model="thisTour.site" ng-options="site.name for site in siteList track by site.id"></select>
  {{thisTour.site}}
  </div>

From documentation

trackexpr: - Used when working with an array of objects. The result of this expression will be used to identify the objects in the array. The trackexpr will most likely refer to the value variable (e.g. value.propertyName). With this the selection is preserved even when the options are recreated (e.g. reloaded from the server).

Also worth noting:

Do not use select as and track by in the same expression. They are not designed to work together.

Scherer answered 16/12, 2014 at 21:35 Comment(4)
OK, I think I get it now, thanks. I'd read about 'track by' in passing but didn't fully understand. This is very comprehensive, thank you.Mudfish
@Mudfish Yeah the documentation link is down. Take a look at the usage section in official ng-options documentation.Scherer
+1 for Do not use select as and track by in the same expression. They are not designed to work togetherDianthus
thanks kazupooot, your comment is very useful to me.Fredi
V
3

This isn't the same thing because objects in javascript are passed by reference.

If you take the first example:

$scope.siteList = [
    { id: 1, name: 'cycling'},
    { id: 2, name: 'walking'},
    { id: 3, name: 'holidays'}
]

$scope.thisTour.site = { id: 2, name: 'walking'};

Then you do this:

$scope.thisTour.site.id = 3;
console.log($scope.siteList[1].id) // 2

In other words, whilst your two objects are equal in value, they aren't the same object. The ngOptions directive sees this, so would set thisTour.site to a blank value because it isn't one of the allowed options.

Google "passing by reference in javascript" to learn more.

Village answered 16/12, 2014 at 21:37 Comment(4)
I guess it should be copy of reference, i dont think there is any pass by reference in javascript. Incase of objects object's references are copied... It is completely different from by ref implementation in many languages.Scherer
Not sure I understand your comment. It's beyond my programming knowledge. All the javascript books I have read say javascript passes objects by reference.Village
function updateObj(someObj){someObj = null;} and var ob = {test:'1'}; updateObj(ob); This will not nullify the object someObj because what is copied to the function is the value of object's reference. Whereas say in c# which supports pass by reference with ref keyword , the originial object inturn will be nullified.Scherer
i was really struggling with binding dropdown selected value but your solution is really damn simple and helped me.Thank you sir for posting such a simple solutionMarrs
B
1

Since you are using the entire object in your select then when Angular does it's comparison it is going to see if the objects are the same in order to set your select. I believe there is a way to change the functionality in how Angular does it's comparisions but I just loop through the select and do my own comparions similar to the below:

$scope.siteList = [
        { id: 1, name: 'cycling'},
        { id: 2, name: 'walking'},
        { id: 3, name: 'holidays'}
    ]
angular.forEach($scope.siteList, function(site, index) {
    if (site.id == 2) {
        $scope.thisTour.site = site;
    }
});

This will set the actual object to your variable allowing it to be set in the select.

Bitchy answered 16/12, 2014 at 21:36 Comment(0)
B
0

Use to ng-init directive .Which is execute initialy at a time we can assign value to ng-model .

<div ng-init="thisTour.site = siteList[position]">
    <select ng-model="thisTour.site" ng-options="site.name for site in  siteList track by site.id"></select>
</div>
Bubonocele answered 3/8, 2016 at 13:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.