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?
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.
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.
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.
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>