Improve this Doc View Source

## Overview

The `ngOptions` attribute can be used to dynamically generate a list of `<option>` elements for the `<select>` element using the array or object obtained by evaluating the `ngOptions` comprehension expression.

In many cases, ngRepeat can be used on `<option>` elements instead of `ngOptions` to achieve a similar result. However, `ngOptions` provides some benefits:

- more flexibility in how the `<select>`'s model is assigned via the `select` `as` part of the comprehension expression
- reduced memory consumption by not creating a new scope for each repeated instance
- increased render speed by creating the options in a documentFragment instead of individually

When an item in the `<select>` menu is selected, the array element or object property represented by the selected option will be bound to the model identified by the `ngModel` directive.

Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can be nested into the `<select>` element. This element will then represent the `null` or "not selected" option. See example below for demonstration.

## Complex Models (objects or collections)

By default, `ngModel` watches the model by reference, not value. This is important to know when binding the select to a model that is an object or a collection.

One issue occurs if you want to preselect an option. For example, if you set the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection, because the objects are not identical. So by default, you should always reference the item in your collection for preselections, e.g.: `$scope.selected = $scope.collection[3]`.

Another solution is to use a `track by` clause, because then `ngOptions` will track the identity of the item not by reference, but by the result of the `track by` expression. For example, if your collection items have an id property, you would `track by item.id`.

A different issue with objects or collections is that ngModel won't detect if an object property or a collection item changes. For that reason, `ngOptions` additionally watches the model using `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute. This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection has not changed identity, but only a property on the object or an item in the collection changes.

Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection if the model is an array). This means that changing a property deeper than the first level inside the object/collection will not trigger a re-rendering.

## `select` `as`

Using `select` `as` will bind the result of the `select` expression to the model, but the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) or property name (for object data sources) of the value within the collection. If a `track by` expression is used, the result of that expression will be set as the value of the `option` and `select` elements.

### `select` `as` and `track by`

Be careful when using `select` `as` and `track by` in the same expression.

Given this array of items on the $scope:

    $scope.items = [{
      id: 1,
      label: 'aLabel',
      subItem: { name: 'aSubItem' }
    }, {
      id: 2,
      label: 'bLabel',
      subItem: { name: 'bSubItem' }
    }];

This will work:

    <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>

    $scope.selected = $scope.items[0];

but this will not work:

    <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>

    $scope.selected = $scope.items[0].subItem;

In both examples, the `track by` expression is applied successfully to each `item` in the `items` array. Because the selected option has been set programmatically in the controller, the `track by` expression is also applied to the `ngModel` value. In the first example, the `ngModel` value is `items[0]` and the `track by` expression evaluates to `items[0].id` with no issue. In the second example, the `ngModel` value is `items[0].subItem` and the `track by` expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value is not matched against any `<option>` and the `<select>` appears as having no selected value.

## Directive Info

- This directive executes at priority level 0.

## Usage

- as attribute:

      <ANY
        ng-model="string"
        ng-options="comprehension_expression"
        [name="string"]
        [required="string"]
        [ng-required="string"]
        [ng-attr-size="string"]>
      ...
      </ANY>

### Arguments

Param | Type | Details
--- | --- | ---
ngModel | string | Assignable AngularJS expression to data-bind to.

ngOptions

comprehension_expression

in one of the following forms:

- for array data sources:
  - `label` `for` `value` `in` `array`
  - `select` `as` `label` `for` `value` `in` `array`
  - `label` `group by` `group` `for` `value` `in` `array`
  - `label` `disable when` `disable` `for` `value` `in` `array`
  - `label` `group by` `group` `for` `value` `in` `array` `track by` `trackexpr`
  - `label` `disable when` `disable` `for` `value` `in` `array` `track by` `trackexpr`
  - `label` `for` `value` `in` `array` | orderBy:`orderexpr` `track by` `trackexpr` (for including a filter with `track by`)
- 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`
  - `label` `disable when` `disable` `for (``key``,` `value``) in` `object`
  - `select` `as` `label` `group by` `group` `for` `(``key``,` `value``) in` `object`
  - `select` `as` `label` `disable when` `disable` `for` `(``key``,` `value``) in` `object`

Where:

- `array` / `object`: an expression which evaluates to an array / object to iterate over.
- `value`: local variable which will refer to each item in the `array` or each property value of `object` during iteration.
- `key`: local variable which will refer to a property name in `object` during iteration.
- `label`: The result of this expression will be the label for `<option>` element. The `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
- `select`: The result of this expression will be bound to the model of the parent `<select>` element. If not specified, `select` expression will default to `value`.
- `group`: The result of this expression will be used to group options using the `<optgroup>` DOM element.
- `disable`: The result of this expression will be used to disable the rendered `<option>` element. Return `true` to disable.
- `trackexpr`: Used when working with an array of objects. The result of this expression will be used to identify the objects in the array. The `trackexpr` will most likely refer to the `value` variable (e.g. `value.propertyName`). With this the selection is preserved even when the options are recreated (e.g. reloaded from the server).

name (optional) | string | Property name of the form under which the control is published.
required (optional) | string | The control is considered valid only if value is entered.
ngRequired (optional) | string | Adds `required` attribute and `required` validation constraint to the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of `required` when you want to data-bind to the `required` attribute.
ngAttrSize (optional) | string | sets the size of the select element dynamically. Uses the ngAttr directive.

## Example

    <script>
    angular.module('selectExample', [])
      .controller('ExampleController', ['$scope', function($scope) {
        $scope.colors = [
          {name:'black', shade:'dark'},
          {name:'white', shade:'light', notAnOption: true},
          {name:'red', shade:'dark'},
          {name:'blue', shade:'dark', notAnOption: true},
          {name:'yellow', shade:'light', notAnOption: false}
        ];
        $scope.myColor = $scope.colors[2]; // red
      }]);
    </script>
    <div ng-controller="ExampleController">
      <ul>
        <li ng-repeat="color in colors">
          <label>Name: <input ng-model="color.name"></label>
          <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label>
          <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button>
        </li>
        <li>
          <button ng-click="colors.push({})">add</button>
        </li>
      </ul>
      <hr/>
      <label>Color (null not allowed):
        <select ng-model="myColor" ng-options="color.name for color in colors"></select>
      </label><br/>
      <label>Color (null allowed):
      <span  class="nullable">
        <select ng-model="myColor" ng-options="color.name for color in colors">
          <option value="">-- choose color --</option>
        </select>
      </span></label><br/>

      <label>Color grouped by shade:
        <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
        </select>
      </label><br/>

      <label>Color grouped by shade, with some disabled:
        <select ng-model="myColor"
              ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
        </select>
      </label><br/>

      Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>.
      <br/>
      <hr/>
      Currently selected: {{ {selected_color:myColor} }}
      <div style="border:solid 1px black; height:20px"
           ng-style="{'background-color':myColor.name}">
      </div>
    </div>

    it('should check ng-options', function() {
      expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
      element.all(by.model('myColor')).first().click();
      element.all(by.css('select[ng-model="myColor"] option')).first().click();
      expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
      element(by.css('.nullable select[ng-model="myColor"]')).click();
      element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
      expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
    });