455

I have read about it in other posts, but I couldn't figure it out.

I have an array,

$scope.items = [
   {ID: '000001', Title: 'Chicago'},
   {ID: '000002', Title: 'New York'},
   {ID: '000003', Title: 'Washington'},
];

I want to render it as:

<select>
  <option value="000001">Chicago</option>
  <option value="000002">New York</option>
  <option value="000003">Washington</option>
</select>

And also I want to select the option with ID=000002.

I have read select and tried, but I can't figure it out.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Andrej Kaurin
  • 11,592
  • 13
  • 46
  • 54
  • https://github.com/angular/angular.js/issues/6564 – Christophe Roussy Dec 01 '14 at 13:53
  • Actually there is a pure AngularJS solution developed by **QuantumUI**. You can find more examples and documentations at [http://quantumui.org/](http://quantumui.org/). – Ramon Drunce Mar 09 '15 at 10:33
  • I highly recommend using [Select2](http://ivaynberg.github.io/select2/#basics), because it will handle this for you. There is even [a directive for AngularJS](https://github.com/angular-ui/ui-select2). – hewstone Jul 24 '14 at 06:13
  • Great overview of `ng-options` syntax _with examples_ [here, from the SO Docs project](https://riptutorial.com/angularjs/example/7755/ngoptions). – ruffin Jun 30 '22 at 14:17

7 Answers7

808

One thing to note is that ngModel is required for ngOptions to work... note the ng-model="blah" which is saying "set $scope.blah to the selected value".

Try this:

<select ng-model="blah" ng-options="item.ID as item.Title for item in items"></select>

Here's more from AngularJS's documentation (if you haven't seen it):

for array data sources:

  • label for value in array
  • select as label for value in array
  • label group by group for value in array = select as label group by group for value in array

for object data sources:

  • label for (key , value) in object
  • select as label for (key , value) in object
  • label group by group for (key, value) in object
  • select as label group by group for (key, value) in object

For some clarification on option tag values in AngularJS:

When you use ng-options, the values of option tags written out by ng-options will always be the index of the array item the option tag relates to. This is because AngularJS actually allows you to select entire objects with select controls, and not just primitive types. For example:

app.controller('MainCtrl', function($scope) {
   $scope.items = [
     { id: 1, name: 'foo' },
     { id: 2, name: 'bar' },
     { id: 3, name: 'blah' }
   ];
});
<div ng-controller="MainCtrl">
   <select ng-model="selectedItem" ng-options="item as item.name for item in items"></select>
   <pre>{{selectedItem | json}}</pre>
</div>

The above will allow you to select an entire object into $scope.selectedItem directly. The point is, with AngularJS, you don't need to worry about what's in your option tag. Let AngularJS handle that; you should only care about what's in your model in your scope.

Here is a plunker demonstrating the behavior above, and showing the HTML written out


Dealing with the default option:

There are a few things I've failed to mention above relating to the default option.

Selecting the first option and removing the empty option:

You can do this by adding a simple ng-init that sets the model (from ng-model) to the first element in the items your repeating in ng-options:

<select ng-init="foo = foo || items[0]" ng-model="foo" ng-options="item as item.name for item in items"></select>

Note: This could get a little crazy if foo happens to be initialized properly to something "falsy". In that case, you'll want to handle the initialization of foo in your controller, most likely.

Customizing the default option:

This is a little different; here all you need to do is add an option tag as a child of your select, with an empty value attribute, then customize its inner text:

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

Note: In this case the "empty" option will stay there even after you select a different option. This isn't the case for the default behavior of selects under AngularJS.

A customized default option that hides after a selection is made:

If you wanted your customized default option to go away after you select a value, you can add an ng-hide attribute to your default option:

<select ng-model="foo" ng-options="item as item.name for item in items">
   <option value="" ng-if="foo">Select something to remove me.</option>
</select>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • Sorry, I created fiddle for this and can't make it work. http://jsfiddle.net/andrejkaurin/xFaS6/ – Andrej Kaurin Oct 24 '12 at 15:30
  • That's probably because fiddle doesn't work well with Angular. I use Plnkr.co for angular... [Here is a working Plunk](http://plnkr.co/edit/vJOljg). – Ben Lesh Oct 24 '12 at 15:51
  • Thanks a lot for effort but look at value of options. It is 0,1,2 instead of '000001', '000002', '000003' – Andrej Kaurin Oct 24 '12 at 15:55
  • 3
    Turns out that those are the indices of the values, this is how angular can allow you to use objects as the value of your select box. Angular is doing a lot of stuff for you behind the scenes with select boxes, and you're not supposed to be worrying about the value attribute on your options. – Ben Lesh Dec 18 '12 at 15:30
  • 1
    Documentation is under "select" on their site: http://docs.angularjs.org/api/ng.directive:select – Ben Lesh Feb 19 '13 at 13:24
  • 26
    "...ngModel is required for ngOptions to work..." was the crux of the problem for me. Good answer. – tristanm May 23 '13 at 02:53
  • 1
    Is it possible to avoid the first empty option (``)? – swenedo Sep 18 '13 at 15:57
  • @swenedo Yes, You'd have to do an ng-init that examined the value and set it to the first option if you didn't have one selected. Something like: `` ... or for more control, you could do that during controller initialization. – Ben Lesh Sep 18 '13 at 17:07
  • The key for me was realizing that looking at the Elements panel in dev tools will always result in seeing values of "0", "1", etc., while outputting the selected value using Angular (via {{items.selected}} in this example) is the right way of testing the value. – evanmcd Jan 10 '14 at 04:17
  • Guys whether the code given below get worked to make the value get selected by default I tried the same but doesn't get worked with angular-1.3.0 version – Arun Apr 30 '14 at 05:25
  • Great explanation. By the way, there is another option if you want to select index of element, not ID as a value. – Denis Jun 17 '14 at 08:11
  • 4
    I'm trying to use the last case (**A customized default option that hides after a selection is made**) but I found some problems. Instead of `ng-if="foo"` I've had to use `ng-if=!foo` to hide the default empty option when other option is selected. Also, the default empty option appear always at bottom of combo list. How I can put it at the beginning of the combo list? – Fran Herrero Apr 20 '15 at 13:14
  • 1
    @FranHerrero things have likely changed since I've made this answer. I'm unsure what the issue you're having is. Perhaps post another question on StackOverflow? – Ben Lesh Apr 23 '15 at 19:34
  • 1
    This no longer works as of 1.4. https://github.com/angular/angular.js/blob/master/CHANGELOG.md#ngoptions – Ray Suelzer Jul 30 '15 at 21:55
90

I'm learning AngularJS and was struggling with selection as well. I know this question is already answered, but I wanted to share some more code nevertheless.

In my test I have two listboxes: car makes and car models. The models list is disabled until some make is selected. If selection in makes listbox is later reset (set to 'Select Make') then the models listbox becomes disabled again AND its selection is reset as well (to 'Select Model'). Makes are retrieved as a resource while models are just hard-coded.

Makes JSON:

[
{"code": "0", "name": "Select Make"},
{"code": "1", "name": "Acura"},
{"code": "2", "name": "Audi"}
]

services.js:

angular.module('makeServices', ['ngResource']).
factory('Make', function($resource){
    return $resource('makes.json', {}, {
        query: {method:'GET', isArray:true}
    });
});

HTML file:

<div ng:controller="MakeModelCtrl">
  <div>Make</div>
  <select id="makeListBox"
      ng-model="make.selected"
      ng-options="make.code as make.name for make in makes"
      ng-change="makeChanged(make.selected)">
  </select>

  <div>Model</div>
  <select id="modelListBox"
     ng-disabled="makeNotSelected"
     ng-model="model.selected"
     ng-options="model.code as model.name for model in models">
  </select>
</div>

controllers.js:

function MakeModelCtrl($scope)
{
    $scope.makeNotSelected = true;
    $scope.make = {selected: "0"};
    $scope.makes = Make.query({}, function (makes) {
         $scope.make = {selected: makes[0].code};
    });

    $scope.makeChanged = function(selectedMakeCode) {
        $scope.makeNotSelected = !selectedMakeCode;
        if ($scope.makeNotSelected)
        {
            $scope.model = {selected: "0"};
        }
    };

    $scope.models = [
      {code:"0", name:"Select Model"},
      {code:"1", name:"Model1"},
      {code:"2", name:"Model2"}
    ];
    $scope.model = {selected: "0"};
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
mp31415
  • 6,531
  • 1
  • 44
  • 34
  • 28
    Dear random user. While this question and it's answer are rather simple and uncomplicated, your organization of code into its proper and respective locations (controller, service, template, data) shows the elegance of AngularJS in it's simplest and default form. Awesome example. – Atticus May 23 '13 at 15:30
  • 1
    This is not how it is meant to be used. `ng-model` should point to **another** variable on your scope, not related with `make`. See the example in https://docs.angularjs.org/api/ng/directive/ngOptions – Dmitri Zaitsev Apr 24 '15 at 05:40
  • @DmitriZaitsev I think you're wrong, just because the angular docs example shows it the way you described doesn't mean that's the only way. Show us why you think it is not supposed to be used this way rather than rubbishing a great example for newbies. – BlueberrySourRaspberry Jun 04 '15 at 16:25
41

For some reason AngularJS allows to get me confused. Their documentation is pretty horrible on this. More good examples of variations would be welcome.

Anyway, I have a slight variation on Ben Lesh's answer.

My data collections looks like this:

items =
[
   { key:"AD",value:"Andorra" }
,  { key:"AI",value:"Anguilla" }
,  { key:"AO",value:"Angola" }
 ...etc..
]

Now

<select ng-model="countries" ng-options="item.key as item.value for item in items"></select>

still resulted in the options value to be the index (0, 1, 2, etc.).

Adding Track By fixed it for me:

<select ng-model="blah" ng-options="item.value for item in items track by item.key"></select>

I reckon it happens more often that you want to add an array of objects into an select list, so I am going to remember this one!

Be aware that from AngularJS 1.4 you can't use ng-options any more, but you need to use ng-repeat on your option tag:

<select name="test">
   <option ng-repeat="item in items" value="{{item.key}}">{{item.value}}</option>
</select>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mattijs
  • 3,265
  • 3
  • 38
  • 35
  • 4
    This answer seems to be exactly what the question is asking for. Every other answer tells the reader not to worry what HTML is generated, but if the select element is in a form, I worry very much. Great answer! – Keith Apr 17 '15 at 04:52
  • 3
    It should be pointed out that this stores the full selected object in the model, not just the key. If you want just the key in the model, you want to use the "item.key as item.value" version. This confused me for a while as I was hung up on what the HTML looked like, not the data that I wanted in the model, which is, for me, the important thing. – mhenry1384 Jul 08 '15 at 12:44
  • @mhenry1384 Yes you are right, about it storing the whole object. I actually like that feature because it gives you access to more than just the ID (if you need it.). Works well for me when I populate lists witha mongo collection and I need some property from the selected item. – Mattijs Feb 29 '16 at 23:09
  • 2
    And how can we detect changes with "ng-change" inside `option` ? – Konstantinos Natsios Oct 24 '16 at 11:04
  • 2
    Regarding the Update, this does not look right. ng-options still works fine in 1.5. It is also still show in documentation. https://docs.angularjs.org/api/ng/directive/select – Jeremy A. West Dec 06 '16 at 14:05
  • For me, the correct syntax for ng-options (with similar data) was `item as item.value for item in items track by item.key`. – Collin Krawll Mar 22 '19 at 23:29
16

The question is already answered (BTW, really good and comprehensive answer provided by Ben), but I would like to add another element for completeness, which may be also very handy.

In the example suggested by Ben:

<select ng-model="blah" ng-options="item.ID as item.Title for item in items"></select>

the following ngOptions form has been used: select as label for value in array.

Label is an expression, which result will be the label for <option> element. In that case you can perform certain string concatenations, in order to have more complex option labels.

Examples:

  • ng-options="item.ID as item.Title + ' - ' + item.ID for item in items" gives you labels like Title - ID
  • ng-options="item.ID as item.Title + ' (' + item.Title.length + ')' for item in items" gives you labels like Title (X), where X is length of Title string.

You can also use filters, for example,

  • ng-options="item.ID as item.Title + ' (' + (item.Title | uppercase) + ')' for item in items" gives you labels like Title (TITLE), where Title value of Title property and TITLE is the same value but converted to uppercase characters.
  • ng-options="item.ID as item.Title + ' (' + (item.SomeDate | date) + ')' for item in items" gives you labels like Title (27 Sep 2015), if your model has a property SomeDate
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tom
  • 26,212
  • 21
  • 100
  • 111
7

In CoffeeScript:

#directive
app.directive('select2', ->
    templateUrl: 'partials/select.html'
    restrict: 'E'
    transclude: 1
    replace: 1
    scope:
        options: '='
        model: '='
    link: (scope, el, atr)->
        el.bind 'change', ->
            console.log this.value
            scope.model = parseInt(this.value)
            console.log scope
            scope.$apply()
)
<!-- HTML partial -->
<select>
  <option ng-repeat='o in options'
          value='{{$index}}' ng-bind='o'></option>
</select>

<!-- HTML usage -->
<select2 options='mnuOffline' model='offlinePage.toggle' ></select2>

<!-- Conclusion -->
<p>Sometimes it's much easier to create your own directive...</p>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Akatsuki Sai
  • 117
  • 1
  • 3
  • 1
    Dont forget your `radix` for `parseInt`: [http://davidwalsh.name/parseint-radix](http://davidwalsh.name/parseint-radix) – GFoley83 Jul 09 '14 at 01:29
6

If you need a custom title for each option, ng-options is not applicable. Instead use ng-repeat with options:

<select ng-model="myVariable">
  <option ng-repeat="item in items"
          value="{{item.ID}}"
          title="Custom title: {{item.Title}} [{{item.ID}}]">
       {{item.Title}}
  </option>
</select>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dmitri Algazin
  • 3,332
  • 27
  • 30
1

It can be useful. Bindings do not always work.

<select id="product" class="form-control" name="product" required
        ng-model="issue.productId"
        ng-change="getProductVersions()"
        ng-options="p.id as p.shortName for p in products"></select>

For example, you fill the options list source model from a REST service. A selected value was known before filling the list, and it was set. After executing the REST request with $http, the list option is done.

But the selected option is not set. For unknown reasons AngularJS in shadow $digest executing does not bind selected as it should be. I have got to use jQuery to set the selected. It`s important! AngularJS, in shadow, adds the prefix to the value of the attr "value" for generated by ng-repeat options. For int it is "number:".

$scope.issue.productId = productId;
function activate() {
    $http.get('/product/list')
       .then(function (response) {
           $scope.products = response.data;

           if (productId) {
               console.log("" + $("#product option").length);//for clarity
               $timeout(function () {
                   console.log("" + $("#product option").length);//for clarity
                   $('#product').val('number:'+productId);

               }, 200);
           }
       });
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
trueboroda
  • 2,650
  • 26
  • 24