Angular.js: ng-options doesn't work well when using objects

Created on 28 Aug 2012  Â·  29Comments  Â·  Source: angular/angular.js

http://jsfiddle.net/ADukg/658/

The example uses ng-options to provide 3 different objects which can be selected. All works until you switch to the next section then comes back. As the implementation uses object comparison, Angular can no longer match the current selected with the datasource.

Conceptually, ng-options should store the 3 selection objects (somewhere) and their lifespan is tied to the "data" object's scope, and not to the ng-options's proxyOptions lifespan.

Also, the comparison should be on a key, and not on the object, in this example, the key is proxyType, there currently is no way to provide the key field to ng-option while still using the object as the value.

more info

Most helpful comment

The way to fix your issue is to use the track by {expression} at the end of the for loop.

<select required ng-model="widget.item" ng-options="i.name for i in items track by i.id"></select>

All 29 comments

BTW: this is our workaround right now

<select ng-model="config.proxy.proxyType"><!-- objects comparison doesn't work well, so use key comparison instead -->
            <option value="None">None</option>
            <option value="Manual">Manual</option>
            <option value="Automatic">Automatic</option>
        </select>

Basically, instead of 3 objects for each type, we now use 1 object to store the sum of all types.

This workaround isn't too bad, just we needed to have type specific serialization logic in the service layer now.

Try this:

<select ng-model="data.selected" ng-options="p.proxyType as p.proxyType for p in proxyOptions">
</select>

And this in the controller:

$scope.data = { selected: 'None' };

http://jsfiddle.net/ADukg/670/

Yeah, that is the workaround. This bug is about the object syntax not working well in practice, and a possible way to fix it.

Also, the comparison should be on a key, and not on the object

This would be a huge help. My application is loading the items from a ReST resource, and the selection is being loaded from another resource.

I suggest "identified by {expression}" as a way to differentiate arrays of object items:

<select required ng-model="widget.item" ng-options="i.name identified by i.id for i in items"></select>

I cannot understand why the select with no empty option shows empty option <option value="?" selected="selected"></option>

Also for this example: http://jsfiddle.net/3y5Pw/

I'm expecting no empty options in the select.

:+1: commented to watch this

Also I made a directive to automatically apply a workaround. See http://jsfiddle.net/ADukg/2226/

@qualidafial Hi, did you find a solution with your Rest resource, because I have the same problem.

@vgrivel Sorry for the late reponse. I wouldn't call it a solution, but rather a workaround.

Basically, after loading the resource, I search through the array of options being used in the select element, and set the resource property to the option that matches. That way you have an identity equals. It's ugly but it works.

@qualidafial i use the same way to do that. Is there any new solution for that out now? I need key comparison in ng-options instead of whole object comparison for array of objects.

@tschiela I haven't seen anything yet.

In my case, I ended up using "select as name for item in items" and only storing the item ID (the select part) in my model.

to deal with providing objects to the filter expression, I used this solution:

http://plnkr.co/btcROVvJHNI2PwWi1o4X

where I take the value of the select, and then pass it to the filter expression after manually converting it to a real object-

I was able to use the "select-key" extension from ngyn, and it worked well.

Would love to see this issue fixed properly in Angular though!

Ran into this same issue. I like the ideas centered around giving an id field for angular to check against.

@Kjuib: me too

The way to fix your issue is to use the track by {expression} at the end of the for loop.

<select required ng-model="widget.item" ng-options="i.name for i in items track by i.id"></select>

That's right, let's close the issue?

It's not present in current documentation (http://docs.angularjs.org/api/ng/directive/select). I had problems recently using track by in ngOptions. It's not used exactly as ngRepeat, but I'm not totally sure.

One more issue, please checkout http://jsfiddle.net/YDdJf/4 Doesn't use city.cp as option value.

It has always painful to developers(newbies) while working with ng-options, like we get empty/blank selected value in select tag and then we try many of the things like to hide empty space and show the first value as selected, but unfortunately its not selected. :(
Especially when we are dealing with JSON object in ng-options, it becomes much tedious.
Here I have done some exercises on that.

Objective: Iterate array of JSON objects through ng-option and set selected first element.

Data:
someNames = [{"id":"1","someName":"xyz"}, {"id":"2","someName":"abc"}]

In select tag I had to show xyz and abc, where xyz must be selected without much efforts.

HTML:


<select class="form-control" name="test" style="width:160px" ng-options="name.someName for name in someNames" ng-model="testModel.test" ng-selected = "testModel.test = testModel.test || someNames[0]">
</select>

By above code sample, you might get out of this exaggeration.

track by is indeed the way to solve this: http://plnkr.co/edit/FIQ1eORODFibHon12rYz?p=preview

Track by does NOT solve it. Track by can only be used by arrays. The OP is reporting issues with objects.

Converting objects to arrays to "solve" the problem with objects is NOT a solution.

@dailytabs, similarly, converting arrays to objects just so your form data works smoother with id's from the backend is a pretty crappy solution too.

Found the answer using resources http://embed.plnkr.co/ZXo8n0jE8r3LsgdhR0QP/preview

Why does AngularJS include an empty option in select? and my default value is empty ?
blow is my code:
.directive('gettheme',function( ){
return {
restrict:"AE",
template:"",
link:function(scope, element, attrs){
scope.theme=['macarons', 'infographic'];
}
}
})

You dont have to write a whole directive for it.
If you use ng-options angular usually make empty first option, but if
you dont want it, you have to define ng-model in your js file like
that:
$scope.option = ['banana','apple'];$scope.selectedOption = 'banana';

Best Regards.

2017-05-05 6:56 GMT+02:00 fionagithub notifications@github.com:

Why does AngularJS include an empty option in select? and my default value
is empty ?
blow is my code:
.directive('gettheme',function( ){
return {
restrict:"AE",
template:"",
link:function(scope, element, attrs){
scope.theme=['macarons', 'infographic'];
}
}
})

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/angular/angular.js/issues/1302#issuecomment-299375264,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AND858Wc3kDgpGkmYTYqBAKCmghWE7MCks5r2qwGgaJpZM4AIH0C
.

For anyone still experiencing this, the track by was breaking the ngOptions. Removing it caused the ngModel binding to work as expected.

Below is my component's code:

<select class="form-control"
        id="{{::$ctrl.elementId}}"
        data-ng-model="$ctrl.selectedKey"
        data-ng-options="item.key as item.label for (key, item) in $ctrl.items">
    <option value="" ng-if="::$ctrl.placeholder">{{::$ctrl.placeholder}}</option>
</select>
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ceymard picture ceymard  Â·  3Comments

brijesh1ec picture brijesh1ec  Â·  3Comments

ashclarke picture ashclarke  Â·  3Comments

nosideeffects picture nosideeffects  Â·  3Comments

piu picture piu  Â·  3Comments