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 such as reducing memory and
increasing speed by not creating a new scope for each repeated instance, as well as providing
more flexibility in how the <select>
's model is assigned via the select
as
part of the
comprehension expression. ngOptions
should be used when the <select>
model needs to be bound
to a non-string value. This is because an option element can only be bound to string values at
present.
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.
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
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.
<ANY
ng-model="string"
[name="string"]
[required="string"]
[ng-required="string"]
[ng-options="comprehension_expression"]>
...
</ANY>
Param | Type | Details |
---|---|---|
ngModel | string |
Assignable angular expression to data-bind to. |
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 |
ngOptions
(optional)
|
comprehension_expression |
in one of the following forms:
Where:
|
<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');
});