I am getting problem while binding my dropdown value with associative array.
Problem is with track by so like when I don't add track by to my dropdown then I have my binding with dropdown and when I add track by then O am unable to auto select dropdown value.
I want to use track by with ng-options so that angular js doesn't add $$hashKey and leverage performance benefit associated with track by.
I am not getting why this behaviour is happening.
Note: I only want to bind name of choices like Pizza or burger for each of my $scope.items and not whole object.
Update: As I understand and with so much trying with current data structure of my $scope.items it is not working with ng-options and I want to use ng-options with track by to avoid generating hash key by Angular js. I also tried ng-change as suggested by @MarcinMalinowski but I am getting key as undefined.
So what should be my data structure of $scope.items so that when I need to access any item from my $scope.items? I can access it without doing loop (like we access items from associative array) like how I can access now with correct data structure and using ngoptions only with track by.
var app = angular.module("myApp", []);
app.controller("MyController", function($scope) {
$scope.items = [
{
"title": "1",
"myChoice" :"",
"choices": {
"pizza": {
"type": 1,
"arg": "abc",
"$$hashKey": "object:417"
},
"burger": {
"type": 1,
"arg": "pqr",
"$$hashKey": "object:418"
}
}
},
{
"title": "2",
"myChoice" :"",
"choices": {
"pizza": {
"type": 1,
"arg": "abc",
"$$hashKey": "object:417"
},
"burger": {
"type": 1,
"arg": "pqr",
"$$hashKey": "object:418"
}
}
}
];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<ul ng-app="myApp" ng-controller="MyController">
<div ng-repeat="data in items">
<div>{{data.title}}
</div>
<select ng-model="data.myChoice"
ng-options="key as key for (key , value) in data.choices track by $index"><option value="">Select Connection</option></select>
</div>
</ul>
The problems in your code are:
1) track by $index
is not supported by ngOptions
, it will result the value of the option
to be undefined
(in your case it will be an $index
of ngRepeat
);
2) track by
doesn't work well with object data-sources (it is supposed to be used with array data-sources), from the docs:
trackexpr: Used when working with an array of objects. The result of this expression will be used to identify the objects in the array.
Of course, you can use ngRepeat
to generate option
elements, but personally, I would prefer using ngOptions
without track by
due to the benefits it has over ngRepeat
.
UPDATE: Here is the code that illustrates how you can change your initial data-source and use track by
to pre-select an option in case the model is an object. But even in the first example console.log()
shows that $$hashKey
was not added to choices
object.
var app = angular.module("myApp", []);
app.controller("MyController", ['$scope', '$timeout', function($scope, $timeout) {
$scope.items = [
{
"title": "1",
"myChoice" :"burger",
"choices": {
"pizza": {
"type": 1,
"arg": "abc"
},
"burger": {
"type": 1,
"arg": "pqr"
}
}
},
{
"title": "2",
"myChoice" :"",
"choices": {
"pizza": {
"type": 1,
"arg": "abc"
},
"burger": {
"type": 1,
"arg": "pqr"
}
}
}
];
$scope.itemsTransformed = angular.copy($scope.items).map(function(item){
delete item.myChoice;
item.choices = Object.keys(item.choices).map(function(choice){
item.choices[choice].name = choice;
return item.choices[choice];
});
return item;
});
//select an option like an object, not a string
$scope.itemsTransformed[1].myChoice = $scope.itemsTransformed[1].choices[0];
$timeout(function() {
//changes a prop in opts array - options are not-re-rendered in the DOM
//the same option is still selected
$scope.itemsTransformed[1].choices[0].arg = "xyz";
}, 3000);
$scope.selectionChanged =function(key, items){
console.log(items); //as we can see $$hashKey wasn't added to choices props
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<ul ng-app="myApp" ng-controller="MyController">
<p>Without track by:</p>
<div ng-repeat="data in items track by data.title">
<div>{{data.title}} - {{data.myChoice}}</div>
<select ng-model="data.myChoice"
ng-options="key as key for (key , value) in data.choices"
ng-change="selectionChanged(key, items)">
<option value="">Select Connection</option>
</select>
</div>
<hr/>
<p>Using track by name to pre-select an option:</p>
<div ng-repeat="data in itemsTransformed track by data.title">
<div>{{data.title}} - {{data.myChoice}}</div>
<select ng-model="data.myChoice"
ng-options="choice as choice.name for choice in data.choices track by choice.name"
ng-change="selectionChanged(key, itemsTransformed)">
<option value="">Select Connection</option>
</select>
</div>
</ul>
UPDATE 2: A simple example that shows us the fact $$hashKey
property is not added to the objects when using ngOptions
without track by
:
var app = angular.module("myApp", []);
app.controller("MyController", ['$scope', '$timeout', function ($scope, $timeout) {
$scope.items = {
"pizza": {
"type": 1,
"arg": "abc"
},
"burger": {
"type": 1,
"arg": "pqr"
}
};
$scope.selectionChanged = function (key, items) {
console.log($scope.items);
};
}]);
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyController">
<hr/>
<p>Example without track by:</p>
<select ng-model="myChoice"
ng-options="key as key for (key , value) in items"
ng-change="selectionChanged(myChoice, items)">
<option value="">Select Connection</option>
</select>
<hr/>
{{myChoice}}
</div>
UPDATE 3: Final result below (that work with angularjs versions < 1.4, for 1.4+ I would recommend changing the data structure as $scope.itemsTransformed
in the first code snippet):
angular.module("myApp", [])
.controller("MyController", ['$scope', function ($scope) {
$scope.items = [
{
"title": "1",
"myChoice": "burger",
"choices": {
"pizza": {
"type": 1,
"arg": "abc"
},
"burger": {
"type": 1,
"arg": "pqr"
}
}
},
{
"title": "2",
"myChoice": "",
"choices": {
"pizza": {
"type": 1,
"arg": "abc"
},
"burger": {
"type": 1,
"arg": "pqr"
}
}
}
];
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyController">
<div ng-repeat="data in items track by data.title">
<div>{{data.title}} {{data.myChoice}}</div>
<select ng-model="data.myChoice"
ng-options="key as key for (key , value) in data.choices">
<option value="">Select Connection</option>
</select>
</div>
</div>
ngOptions
doesn't create new scope like ngRepeat
directive per item therefore you don't need to take care about to get rid of $$hashKey
I would use ng-repeat
to iterate on <option>
(suppose you don't create long lists):
<select ng-model="data.myChoice">
<option value="">Select Connection</option>
<option ng-repeat="(key , value) in data.choices track by key" ng-value="key" title="{{key}}"
>{{key}}</option>
</select>
Working Demo Fiddle
Take look on this issue: github.com/angular/angular.js/issues/6564 - ng-options track by and select as are not compatible
I believe this issue still exists so suggest you to use ngRepeat
with track by
instead. For small list there is no performance penalty
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With