AngularJS ngOptions track by does not set entire object to ngModel - angularjs

Here my json object :
$scope.todos = [{
name: 'angular',
field: ['a', 'b', 'c', 'd'],
id: 1
}, {
name: 'asd',
field: ['a', 'b', 'c', 'd', 'e'],
id: 2
}];
I give select as :
<select ng-model="dashboard.type" ng-options="item as item.name for item in todos track by item.name"></select>
Now when an option is chosen i want another select which iterates upon ngModel of previous select. The next looks like :
<select ng-model="dashboard.label" ng-options="item as item for item in dashboard.type.field"></select>
It works fine when i choose the option manually but when their is a value in dashboard.type (eg: $scope.dashboard.type.name = 'qwe') in my script, it uses track by to choose the option and save it in dashboard.type, instead of saving the entire object it just saves the value used in track by option.
Value for dashboard.type:
when i choose option manually :
{"name":"qwe","field":["a","b","c","d","f"],"id":3}
When track by is used :
{"name":"qwe"}
NOTE: I cannot use track by item . It has to be a property of the object item. It can either be name or id.
Here's a plnkr.
Edit :
As pointed out by many, i would like to clarify that I can not initialise the object to any value of todos list. This is because dashboard.type.name value would be different every single time and todos list vary from 10 to 100's of objects. Now I have to iterate the list todos check if todos[index].name==someName and assign the relevant object. I am actively trying to avoid this soution (because there has to be a better way).
NOTE : I also tried using ng-change and assign the actual object to dashboard.type.name but that works only when option is selected manually ,which already seems to be working fine. It does not assign the object when track by is used.
IMPORTANT: $scope.dashboard.type.name = 'qwe' is just an example. Value of $scope.dashboard.type.name may change every single time the page is loaded.
PS: Also i should have mentioned this earlier.Sorry, My bad!

This issue is not because of track by. Setting a default value by doing $scope.dashboard.type.name = 'qwe', causes ng-model to look like $scope.dashboard.type = {name : 'qwe'}. Instead of selecting the default value this way, you should use $scope.dashboard.type = $scope.todos[2];. This will make sure the entire object is set to $scope.dashboard.type.
Edited plunker

To initialize the modal by a default value you can use ng-init. For example
<select ng-init="dashboard.type = todos[2]" ng-model="dashboard.type" ng-options="item as item.name for item in todos track by item.name">
</select>

call this function on ng-init
in html
ng-init ="initialize();"
in js
$scope.dashboard={};//initialize object
$scope.dashboard.type ={}//initialize object
$scope.initialize =function(){
//no need to iterate if it is certain that your Id and index is always same suppose your initial id is 3
//then
var currId =3;//suppose initial id is there
$scope.dashboard.type =$scope.todos[currId]
}
here is codepen for it
http://codepen.io/vkvicky-vasudev/pen/MeexmK

Related

Default Value for Select with ng-options

jsFiddle: http://jsfiddle.net/shandylion/2r3z1uma/
I need to display a list of objects as a list of dropdowns, where changing a dropdown changes the corresponding element in the list. Ideally, each dropdown should default to that element.
So, with the following data:
$scope.allPeople = [{"name":"Ann", "Age":10},
{"name":"Barb", "Age":20},
{"name":"Carl", "Age":30}];
$scope.selectedPeople = [{"name":"Ann", "Age":10},
{"name":"Carl", "Age":30}];
and the following HTML:
<div ng-repeat="selectedPerson in selectedPeople">
<select ng-options="person as person.name for person in allPeople"
ng-model="selectedPeople[$index]"
name="select-{{$index}}" id="select-{{$index}}"></select>
</div>
there should be two dropdowns, with the first defaulting to "Ann" and the second to "Carl".
I've tried using ng-selected, but that only seems to work with <option> tags, not inside a <select>. Other Stack Overflow posts suggest we should always use ng-options and never an <option> with an ng-repeat, so I'm stuck as to how to get this to default to the appropriate value.
Here's a sample plunker, which has a working solution as well as the faulty one described below.
The pitfall with dropdowns is that when you want to preselect a value from a list of options, the selected model should be a reference pointing to the same object from the bound list. Say you have:
var list = [{ id: 1, value: 'first'}];
var model = { id: 1, value: 'first' };
<select ng-options="item.value for item in list" ng-model="model"></select>
This will not preselect the dropdown, because list[0] and model are not the same object, although they look alike. If you set ng-model to list[0] it should be preselected just fine.
Since you're using person.name for person in allPeople, the selected option returns person.name as value. You're presetting your $scope.selectedPeople array's values as objects, which does not match with what your select list will return, and hence your initial values are not set properly.
If you want to have person.name as the value of each option, you should change your $scope.selectedPeople to this:
$scope.selectedPeople = ["Ann", "Carl"];
On the other hand if you want to return person as the value of each option, change your ng-options expression to person as person.name for person in allPeople, which means you return person and show it as person.name for each person in $scope.allPeople.

Can't get Angular select to update with selected value

I'm using selects to allow a user to switch between events and years. Each change will pull appropriate data from the server, return and update the page. However, the select box goes from the selected value to an empty value. I've looked at numerous solutions and they aren't working.
<select
ng-model="eventName"
ng-options="item.value for item in eventOptions track by item.value"
ng-change="changeEvent(eventName.value)">
</select>
This is the changeEvent function:
$scope.changeEvent = function(eventName){
$scope.eventName = eventName; //wrongly assumed this would update the selected value
$scope.getData($scope.eventName,$scope.eventYear); //this returns the json - correct
$scope.updateSelected(); //meant to update the select field value on the page - fails
};
$scope.eventName or $scope.eventYear values will properly update on a change, but has no effect on the page. The selects just empty of a selected value.
UPDATED (with corrected code)
I wanted to post the changes I made more clearly than the comment allows.
I removed the object param "value" from the options and the argument from the function call (eventName.value).
<select
ng-model="eventName"
ng-options="item for item in eventOptions track by item"
ng-change="changeEvent()">
</select>
And the changeEvent function gets simplified to:
$scope.changeEvent = function(){
$scope.getData($scope.eventName,$scope.eventYear);
};
It all works as expected! Thanks to all, especially Delta who got me looking at it the right way.
So, your event name variable is set to be item. not item.value so instead of passing in changeEvent(eventName.value) try passing in changeEvent(eventName). either way the value you are passing into your method doesnt match the value of your model's variable
item.value for item in eventOptions track by item.value
so for this statement, you are saying make my options ng-model=item but make their value=item.value so they show what you want them to show but still have all the information you need from each one.
Upon further inspection is looks like you dont need:
$scope.eventName = eventName;
$scope.updateSelected();
Angular should be updating your eventName for you, you just need to call the change method.
I'm not sure you need:
ng-change="changeEvent(eventName.value)"
if you use ng-options the model will be updated in the scope automatically on selection. You could watch the value in the controller if you want to do other stuff when it changes:
$scope.$watch('eventName', function() {
//do stuff
});

ng-model and ng-options not matching up?

I have a method in my resources object that comes in as:
resources.type
otherstuff: 'more strings'
type:'specifictype'
morestuff: 'morestuff'
The user can change this type with a dropdown / through another call that gets a list of all possible types which looks like resourceList.types which has a list of objects like this json
types:
[
{name:'resourcetype1'},
{name:'resourcetype2'},
etc...
],
my html looks like:
<select ng-model="resources.type" ng-options="name.name for name in resourceList.types">
</select>
The select/drop down box populates with my resourceList.type stuff but when the page loads the ng-model doesn't set to the already selected resource type. It actually selects a blank entry at the top of the drop down when you click. Is angular picky about this? How can I get the ng-model to behave the way I want it to?
I tried messing around with ng-options with the different ways of getting the repeat but I feel like this is more how angular connects the model. Does it just match the string to the ng-options list?
Here's the plnkr as you can see it's not defaulting to type1
http://plnkr.co/edit/NyWACtFQuyndR6CG8lpN?p=info
In Angular, the model is the single source of truth.
This means that if you want a value selected (and bound to your ngModel) you need to assign it to the model:
<select ng-model="resources.type"
ng-options="type.name as type.name for type in resourceList.types">
</select>
$scope.resources = {...};
$scope.resourceList = {
...
types: [
{name: 'resourcetype1'},
{name: 'resourcetype2'},
...
]
};
// Set the model to the desired value
$scope.resources.type = $scope.resourceList.types[0].name;
See, also, this short demo.
You don't have to set your model's value to the reference object in resourceList. In fact, the accepted answer works fine without this line:
$scope.resources.type = $scope.resourceList.types[0].name;
How is it working? Thanks to the "as" notation in the ngOptions. Without the "as", the match is made on the full type element, which is an object, so the match is made on the reference's object, not the name's value.
With the "as" the match is made on the element's property, name.
I've forked the plunker: http://plnkr.co/edit/kORfxGdsWBUlFWHXp6Ry?p=preview
in my case it didnt work since ngOptions was an array of integers and i was trying to set ngModal to string type (2the year 2014).
the solution is simple: parseInt function

Can't get Angular-xeditable Editable-Select to Select the current item when it is bound to a nested JSON object

I can't get the Angular-xeditable Editable-Select to Select the current item when it is bound to a nested JSON object. Data all saves fine, but it just doesn't show up the currently selected item in the Select box, which is driving me nuts, as I am sure I am missing something obvious. I have created a JSFiddle here:
http://jsfiddle.net/NfPcH/1031/
This is the code:
<span editable-select="data.organisation.OrganisationType"
e-ng-options="type.Name for type in data.types">
{{data.organisation.OrganisationType.Name}}
</span>
These are the 2 data objects:
$scope.data.organisation = {
"Id":1,
"Name":"My Organisation",
"OrganisationType":{"Id":2,"Name":"Internal"}
}
$scope.data.types = [
{"Id":1,"Name":"Client"},
{"Id":2,"Name":"Internal"},
{"Id":3,"Name":"Cold"}
]
It works fine when I bind it to the ID within the Nested Object, but then it only changes the Id in my nested object, and I then have to manually filter and change the Name part of the object, which works, but I am sure there must be a better way.
When the item is selected $data is loaded with the id of the item selected. You can use this to retrieve the value after the selection is made.
<span editable-select="data.organisation.OrganisationType"
e-ng-options="type.Name for type in data.types">
{{ findTypeName($data) || data.organisation.OrganisationType.Name}}
</span>
You can then define findTypeName in your controller
$scope.findTypeName = function(id) {
var found = $scope.data.types.filter(function(t){
return (t.Id === id);
});
return found.length ? found[0].Name : null;
};
The trick resides on using the object on the select's ng-options (e.g. 'obj as obj.title') and not a property of the object, so that when an item gets selected, it will assign the whole object to the model and not the property.
The problem with this approach is, that you cannot just compare objects (because comparing an object always compares their reference, not the "contents", so that obj1 == obj2 will never be true, unless they are exact the same object), so the other trick needed here is to use track by <some-id-property> on the ng-options. So your editable would look like this:
<span editable-select="data.organisation.OrganisationType"
e-ng-options="type as type.Name for type in data.types track by type.Id">
{{data.organisation.OrganisationType.Name}}
</span>
Beware that on your JSFiddle you're using a very old version of angular (where track by is not supported), so I created a new one using 1.2, which is the oldest angular version supporting track by:
http://jsfiddle.net/NBhn4/170/

ng-options predefined option Object

Have a look at my example
http://plnkr.co/edit/21ewxVIaRm4IHyF3TgoD?p=preview
I need the option object to be stored as ng-model so i use for ng-options ng-options="m as m.name for m in list"
However, if i set ng-model manually as the object it does not select the correct option in the dropdown list by default. It stores the data correctly, but it only pre-selects an option if use this expression ng-options="m.name as m.name for m in list" but this stores a string on the model instead of the options object.
Am I doing something incorrectly?
Goal:
set ng-model to the default option object and have it be automatically selected in the dropdown.
Updated plunker.
For the version you are using (1.0.8), you would have to resolve the object using a loop:
$scope.selected = {
name:"Something"
}
$scope.setSelected = function() {
angular.forEach($scope.list, function(item) {
if (item.name == $scope.selected.name) {
$scope.selected = item;
}
})
}
$scope.setSelected();
More recent versions, have track by syntax supported in the ng-options. So you could write:
ng-options="m.name for m in list track by m.name"
And this would set the object that matches the predicate.
As Davin Tryon has already mentioned, using track by is the correct way of doing it, however, I see some people using track by and 'as' in the same ng-options string, which will not work. using track by should also cater the need of using 'as'

Resources