Can't set selected value of ng-options

All we need is an easy explanation of the problem, so here it is.

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?

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

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.

Method 2

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.

Method 3

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.

Method 4

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>

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply