How to set the value attribute for select options?
Asked Answered
M

8

72

Source JSON data is:

[
  {"name":"Alabama","code":"AL"},
  {"name":"Alaska","code":"AK"},
  {"name":"American Samoa","code":"AS"},
  ...
]

I try

ng-options="i.code as i.name for i in regions"

but am getting:

<option value="?" selected="selected"></option>
<option value="0">Alabama</option>
<option value="1">Alaska</option>
<option value="2">American Samoa</option>

while I am expecting to get:

<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AS">American Samoa</option>

So, how to get value attributes and get rid of "?" item?

By the way, if I set the $scope.regions to a static JSON instead of AJAX request's result, the empty item disappears.

Mercurialize answered 10/12, 2012 at 15:12 Comment(2)
possible duplicate of Why does angularjs include an empty option in selectSaving
Use - region in regions track by region.code to set the value attribute to the region.code value ;)Eldreeda
S
75

What you first tried should work, but the HTML is not what we would expect. I added an option to handle the initial "no item selected" case:

<select ng-options="region.code as region.name for region in regions" ng-model="region">
   <option style="display:none" value="">select a region</option>
</select>
<br>selected: {{region}}

The above generates this HTML:

<select ng-options="..." ng-model="region" class="...">
   <option style="display:none" value class>select a region</option>
   <option value="0">Alabama</option>
   <option value="1">Alaska</option>
   <option value="2">American Samoa</option>
</select>

Fiddle

Even though Angular uses numeric integers for the value, the model (i.e., $scope.region) will be set to AL, AK, or AS, as desired. (The numeric value is used by Angular to lookup the correct array entry when an option is selected from the list.)

This may be confusing when first learning how Angular implements its "select" directive.

Schreibman answered 10/12, 2012 at 20:33 Comment(8)
I find it confusion, and a lack of transparency, that the HTML doesn't reflect the value set in the scope...Pickled
It's a different way of thinking. You are actually selecting a region object, not a region.code string. Once the user has selected something, the region object will be selected. Then when you want the code later on, just access region.code. Hope that helps.Perishing
Can someone explain what exactly the syntax "region.code as region.name" is actually "saying"... it just seems to make little sense to me. That "as" keyword just throws me completely, because I think it has no grammatical use in inferring any kind of meaning whatsoever.Unbridle
@ProxyTech, I think of it this way: use region.code for the values to assign to the model, but show region.name to the user. The API docs refer to the region.name part as the label.Schreibman
@MarkRajcok It's my C# paradigm coming through to cause confusion with the "as" keyword being used for casting from type to another. Then there's my "English language" paradigm, where the keyword "as" means that "region.came" is interacting/superseding "region.code"... completely not the case in the context. Simply very poor syntax in my opinion.Unbridle
How would you for this solution implement condition if only one preselect it?Lentha
For me this should have been marked as an answer. Alas I cannot do it.Underlayer
This worked for me. But one confusing thing in the example is that the "region" used in the ng-options tag is not the same as the "region" used in the ng-model tag. Probably should have used two different words to make it more clear.Qua
W
26

You can't really do this unless you build them yourself in an ng-repeat.

<select ng-model="foo">
   <option ng-repeat="item in items" value="{{item.code}}">{{item.name}}</option>
</select>

BUT... it's probably not worth it. It's better to leave it function as designed and let Angular handle the inner workings. Angular uses the index this way so you can actually use an entire object as a value. So you can use a drop down binding to select a whole value rather than just a string, which is pretty awesome:

<select ng-model="foo" ng-options="item as item.name for item in items"></select>

{{foo | json}}
Welby answered 10/12, 2012 at 15:36 Comment(2)
although your first example produces a working/correct select list, and you cautioned against using it, note that it doesn't work well with the rest of Angular. E.g., this won't work as expected: <a ng-click="foo = 'AK'">select AK</a>. In general, the value attribute probably shouldn't be used with the Angular select directive.Schreibman
This is a fantastic way around a long running bug regarding track by and select not working together. See here for the bug report: github.com/angular/angular.js/issues/6564Contrariety
U
22

If you use the track by option, the value attribute is correctly written, e.g.:

<div ng-init="a = [{label: 'one', value: 15}, {label: 'two', value: 20}]">
    <select ng-model="foo" ng-options="x for x in a track by x.value"/>
</div>

produces:

<select>
    <option value="" selected="selected"></option>
    <option value="15">one</option>
    <option value="20">two</option>
</select>
Unlikelihood answered 30/7, 2014 at 14:25 Comment(1)
This works perfect in Angular 1.3 kittyminky, check your syntax maybe. - This should be the selected answer.Eldreeda
A
7

If the model specified for the drop down does not exist then angular will generate an empty options element. So you will have to explicitly specify the model on the select like this:

<select ng-model="regions[index]" ng-options="....">

Refer to the following as it has been answered before:

Why does AngularJS include an empty option in select? and this fiddle

Update: Try this instead:

<select ng-model="regions[index].code" ng-options="i.code as i.name for i in regions">
</select>

or

<select ng-model="regions[2]" ng-options="r.name for r in regions">
</select>

Note that there is no empty options element in the select.

Amylene answered 10/12, 2012 at 15:36 Comment(0)
A
5

You could modify you model to look like this:

$scope.options = {
    "AL" : "Alabama",
    "AK" : "Alaska",
    "AS" : "American Samoa"
  };

Then use

<select ng-options="k as v for (k,v) in options"></select>
Agnate answered 10/12, 2012 at 15:38 Comment(1)
This variant does not work. It produces either options with the same numeric value and text or options with a numeric value and empty text dependent on order: "k as v" or "v as k"Mercurialize
M
4

It appears it's not possible to actually use the "value" of a select in any meaningful way as a normal HTML form element and also hook it up to Angular in the approved way with ng-options. As a compromise, I ended up having to put a hidden input alongside my select and have it track the same model as my select, like this (all very much simplified from real production code for brevity):

HTML:

<select ng-model="profile" ng-options="o.id as o.name for o in profiles" name="something_i_dont_care_about">
</select>
<input name="profile_id" type="text" style="margin-left:-10000px;" ng-model="profile"/>

Javascript:

App.controller('ConnectCtrl',function ConnectCtrl($scope) {
$scope.profiles = [{id:'xyz', name:'a profile'},{id:'abc', name:'another profile'}];
$scope.profile = -1;
}

Then, in my server-side code I just looked for params[:profile_id] (this happened to be a Rails app, but the same principle applies anywhere). Because the hidden input tracks the same model as the select, they stay in sync automagically (no additional javascript necessary). This is the cool part of Angular. It almost makes up for what it does to the value attribute as a side effect.

Interestingly, I found this technique only worked with input tags that were not hidden (which is why I had to use the margin-left:-10000px; trick to move the input off the page). These two variations did not work:

<input name="profile_id" type="text" style="display:none;" ng-model="profile"/>

and

<input name="profile_id" type="hidden" ng-model="profile"/>

I feel like that must mean I'm missing something. It seems too weird for it to be a problem with Angular.

Moorland answered 29/8, 2013 at 5:2 Comment(1)
You can use <input name="profile_id" type="hidden" value="{{profile}}"/> also. It works.Draconic
P
1

you can use

state.name for state in states track by state.code

Where states in the JSON array, state is the variable name for each object in the array.

Hope this helps

Puddling answered 21/10, 2014 at 6:35 Comment(0)
D
-2

Try it as below:

var scope = $(this).scope();
alert(JSON.stringify(scope.model.options[$('#selOptions').val()].value));
Distich answered 22/12, 2014 at 3:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.