AngularJS select options with id and array index - javascript

I want to have a drop down select box with options coming from an array and I want to have access to the some property of the selected option and the array index.
So, for example I have an array:
selectOptions = [{id: 1, name: 'name1'}, {id: 2, name: 'name2'}, {id: 3, name: 'name3'}];
and when one of the options is chosen I want to have access to the selected option's id and index position in selectOptions.
Right now I have this for selecting the id:
<select name="currentlySelected" ng-model="selectedId" ng-options="cat.id as cat.name for cat in selectOptions" ng-change="change(selectedId)">
</select>
But I also want to send as an input to the change function the index of the selected option in the selectOptions array. I want to be able to do this:
$scope.change = function ($parentId, $arrayId) {
$scope.currentlySelected = $scope.selectOptions[$arrayId];
$scope.selectedId = $parentId;
};
Does someone know how to do this?

Before I give you a solution I want to first suggest that you dont do what you are trying to do. You are basically keeping track of the same thing twice. You want the selected object and the selected id. If you keep track of the selected object you always have the id as well. I would suggest you try to keep track of just the selected object. When you need the id just use object.id
Why dont you select the object and then sync the property:
<select name="currentlySelected" ng-model="selectedItem"
ng-options="cat as cat.name for cat in selectOptions"
ng-change="change()">
</select>
Then in your code
$scope.change = change;
function change() {
$scope.selectedId = $scope.selectedItem && $scope.selectedItem.id
}

You can use the object as the value reference:
<select name="currentlySelected" ng-model="selectedId" ng-options="cat as cat.name for cat in selectOptions"> <!-- You don't need the ng-change, it's not neccecary -->
And in your controller, you need to save a the reference to the object in the array:
$scope.change = function ($arrayId) {
$scope.currentlySelected = $scope.selectOptions[$arrayId];
console.log($scope.selectedId); // THE MODEL HOLES THE OBJECT ITSELF
};
$scope.change(1); // EXAMPLE for changing the value from the code

Check the sample below, this should help you to get things done
ng-options="{catId: cat.id, index: idx} as cat.name for (idx, cat) in selectOptions"
angular.module('app', []).run(function($rootScope){
$rootScope.selectOptions = [{id: 1, name: 'name1'}, {id: 2, name: 'name2'}, {id: 3, name: 'name3'}];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<select name="currentlySelected" ng-model="selectedId" ng-options="{catId: cat.id, index: idx} as cat.name for (idx, cat) in selectOptions">
</select>
<p>
selectedId: {{selectedId}}
</p>
</div>

Related

AngularJS: SELECT Option not selected when ng-model is an array

How can I get the Broken example (below) to work without changing it from an array (as I have a lot of code that depends on it being an array in other parts of the application)?
I'd like to continue using an array but I'd like the correct select option to show when the model changes. The model changes correctly when the button is clicked but the option isn't being found in the select (ng-model and ng-options just aren't matching up when it's an array).
angular.module('app', [])
.controller('MainCtrl', ['$scope', function($scope) {
$scope.valueWorking = 'value1';
$scope.selectFilterWorking = [{name: 'name1', value: 'value1'},{name: 'name2', value: 'value2'}];
$scope.valueBroken = ['value1'];
$scope.selectFilterBroken = [{name: 'name1', value: ['value1']},{name: 'name2', value: ['value2', 'value3']}];
}]);
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="app" ng-controller="MainCtrl">
<strong>Working</strong> (SELECT <i>is</i> updated when ng-model is a string):<br>
<select ng-model="valueWorking"
ng-options="select.value as select.name for select in selectFilterWorking">
<option value="">Select an Option</option>
</select>
<button ng-click="valueWorking='value2'">Assign to String of 'value2'</button> {{valueWorking}}<br><br>
<strong>Broken</strong> (SELECT <i>not</i> updated when ng-model is an array):<br>
<select ng-model="valueBroken"
ng-options="select.value as select.name for select in selectFilterBroken">
<option value="">Select an Option</option>
</select>
<button ng-click="valueBroken=['value2','value3']">Assign to Array of ["value2","value3"]</button> {{valueBroken}}
</div>
Upon clicking 'Run code snippet' above, notice that the initial option is not selected (on the Broken select, when it's an array), but is initially selected on the Working select when it's a string. The correct option is also not shown when 'Assign to Array of ["value2"]' is clicked.
UPDATE: I'm trying to get the OPTIONS ($scope.selectFilterBroken) to match the MODEL, rather than the other way around - hence my need to keep the model as an array. The button click is merely to simulate the other parts of the application that manipulate the array.
Any help would be great. Thanks and please.
Objects in Javascript are not equal even though they look identical but strings or other primitive types are. For example, var a = [1]; var b = [1]; a === b returns false. However, if you do JSON.stringify(a) === JSON.stringify(b), it will return true.
When you assign ['value2'] to valueBroken, valueBroken is redeclared as a new array so it doesn't match with the one in the selectFilterBroken.
To fix this, we wanna find a way to assign the reference of ['value2'] to valueBroken. I wrote a function to find which array in selectFilterBroken matches the one we want by comparing their stringified versions. Once found, assign the reference of the one in the selectFilterBroken to valueBroken.
Without knowing what exactly is going on in your app, it is hard to guess if the solution fits you, but you can go from there.
angular.module('app', [])
.controller('MainCtrl', ['$scope', function($scope) {
$scope.valueWorking = 'value1';
$scope.selectFilterWorking = [{name: 'name1', value: 'value1'},{name: 'name2', value: 'value2'}];
$scope.valueBroken = ['value1'];
$scope.selectFilterBroken = [{name: 'name1', value: ['value1']},{name: 'name2', value: ['value2']}];
$scope.selectBroken = function(valueAry){
for(var i = 0, m = $scope.selectFilterBroken.length; i < m; i++){
if(JSON.stringify($scope.selectFilterBroken[i].value) === JSON.stringify(valueAry)){
$scope.valueBroken = $scope.selectFilterBroken[i].value;
break;
}
}
}
}]);
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="app" ng-controller="MainCtrl">
<strong>Working</strong> (SELECT <i>is</i> updated when ng-model is a string):<br>
<select ng-model="valueWorking"
ng-options="select.value as select.name for select in selectFilterWorking">
<option value="">Select an Option</option>
</select>
<button ng-click="valueWorking='value2'">Assign to String of 'value2'</button> {{valueWorking}}<br><br>
<strong>Broken</strong> (SELECT <i>not</i> updated when ng-model is an array):<br>
<select ng-model="valueBroken"
ng-options="select.value as select.name for select in selectFilterBroken">
<option value="">Select an Option</option>
</select>
<button ng-click="selectBroken(['value2'])">Assign to Array of ["value2"]</button> {{valueBroken}}
</div>
The reason its not binding is because the array value on $scope.valueBroken is different from the arrays in the $scope.selectFilterBroken. For them to match they have to be in the same array. For example, I have edited your code and assigned the value of an item in $scope.selectFilterBroken to $scope.valueBroken. And it is working fine now.
angular.module('app', [])
.controller('MainCtrl', ['$scope', function($scope) {
$scope.valueWorking = 'value1';
$scope.selectFilterWorking = [{name: 'name1', value: 'value1'},{name: 'name2', value: 'value2'}];
$scope.selectFilterBroken = [{name: 'name1', value: ['value1']},{name: 'name2', value: ['value2']}];
$scope.valueBroken = $scope.selectFilterBroken[0].value;
}]);
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="app" ng-controller="MainCtrl">
<strong>Working</strong> (SELECT <i>is</i> updated when ng-model is a string):<br>
<select ng-model="valueWorking"
ng-options="select.value as select.name for select in selectFilterWorking">
<option value="">Select an Option</option>
</select>
<button ng-click="valueWorking='value2'">Assign to String of 'value2'</button> {{valueWorking}}<br><br>
<strong>Broken</strong> (SELECT <i>not</i> updated when ng-model is an array):<br>
<select ng-model="valueBroken"
ng-options="select.value as select.name for select in selectFilterBroken">
<option value="">Select an Option</option>
</select>
<button ng-click="valueBroken=['value2']">Assign to Array of ["value2"]</button> {{valueBroken}}
</div>

AngularJS filter an array of objects by text input depending on what select option is chosen

I'm having trouble getting a particular Angularjs filtering setup working that involves a text input, ng-repeat and a select, and love any assistance anyone
can provide.
I have an array of objects like so:
[{ id: '1', firstName: 'Charles', lastName: 'Charlston' }, ...]
I'm filtering the array by text input like so:
<input ng-model="searchText" />
<div ng-repeat="person in people | filter : searchText" ...
Currently, this sorts the array of people objects for "any" property value and it works fine.
What i'm trying to achieve is be able to change the property that my <input ng-model="searchText" /> filters the people array by, based on what <option id="1" label="lastName" selected>Last Name</option> is selected.
My select looks like this:
<select class="filterSelect"
ng-model="selectedPropertyOption"
ng-options="prop.name for prop in peopleProperties"></select>
peopleProperties looks like this:
$scope.peopleProperties = [{id: "1", name: "firstName"}, ...];
So instead of typing: "charles" in the input and getting results that match either property id, fistName or lastName, I need to be able to choose an option from a select where the option is a property name like "firstName", that I want to filter by. Then, whatever is typed in the input would only filter objects based on which option was selected.
I hope this makes sense enough! Any guidance would be much appreciated, thank you in advance!
This is one of those times where it's inconvenient and kinda "dirty" (although possible) to specify the filter purely in the View.
What we need is a filter object that can take the following forms:
$scope.filter = { firstName: "foo" };
// or
$scope.filter = { lastName: "foo" };
// or - to reset the "filterBy" property
$scope.filter = { $: "foo" }; // or just "foo"
This isn't easy to do in the expression, so it's better to do in the controller:
$scope.filter = {$: undefined}; // initial non-filtering value
$scope.setFilter = function(){
$scope.filter = {};
$scope.filter[$scope.selectedPropertyOption || '$'] = $scope.searchText;
};
Then, invoke the setFilter function on every change of searchText of selectedPropertyOption:
<input ng-model="searchText" ng-change="setFilter()" />
<select ng-model="selectedPropertyOption"
ng-options="prop.name as prop.name for prop in peopleProperties"
ng-change="setFilter()">
<option value="">All Fields</option>
</select>
<div ng-repeat="person in people | filter: filter">
{{person}}
</div>
Demo

Angular: Get the index of the dropdown item

I have the following in my view
<div>
<select ng-model="obj.arr[otherObj.variable]" ng-change="otherObj.variable=SOMETHING">
<option ng-repeat="label in obj.arrs">{{label}}</option>
</select>
</div>
Without the ng-change attribute, this code does what I want when otherObj.variable is one of the indexes of the obj.arr - it selects the correct item in the list.
What I want in addition to this is to set otherObj.variable to the index of the array item that is picked when the dropdown variable is changed. So, if the second value in the dropdown is picked then otherObj.variable should be set to 1. I tried to do this with a
ng-change="otherObj.variable=SOMETHING"
Problem is., I don't know what that SOMETHING should be. Am I doing this right?
EDIT
My requirements are
Select the top option in the dropdown by default
select the appropriate item in the array depending on the value of otherObj.variable (this gets set by some external code so if I come to the page with this value set then I want the correct option selected)
Make sure otherObj.variable is updated if I change the value in the dropdown.
angular.module('selects.demo', [])
.controller('SelectCtrl', function($scope){
$scope.values = [{
id: 1,
label: 'aLabel',
}, {
id: 2,
label: 'bLabel',
}];
$scope.selectedval = $scope.values[0];
});
<script src="https://code.angularjs.org/1.3.15/angular.js"></script>
<div ng-app="selects.demo">
<div ng-controller="SelectCtrl">
<p>Using ngOptions without select as:</p>
<select ng-model="selectedval" ng-options="value.label for value in values"></select>
<p>{{selectedval}}</p>
<p>Using ngOptions with select as statement: (this will return just the id in the model)</p>
<select ng-model="selectedval2" ng-options="value.id as value.label for value in values"></select>
<p>{{selectedval2}}</p>
</div>
</div>
Sorry if my comment was a little cryptic. Select elements like other form elements are actually directives in AngularJS, so they do a lot of stuff for you automatically. You don't need to use an ngChange to populate the ngModel associated with your select element. AngularJS will handle that for you.
Also, you can use ngOptions instead of ngRepeat on select elements to generate the values automatically on options.
Assuming that you have an object with values:
$scope.values = [{
id: 1,
label: 'aLabel',
}, {
id: 2,
label: 'bLabel',
}];
You would write:
<select ng-model="selectedval" ng-options="value.label for value in values"></select>
Now your ngModel is going to be bound to the selected element. It will be set with the value of the object that was chosen. If you add {{selectedval.id}} to your view, it will display the id of the selected element.
If you want to set the value to the first item, in your controller, you would add:
$scope.selectedval = $scope.values[0];
If you want to update some property on $scope.values based on the selected value, you could use something like:
$scope.addActiveProp = function() {
var selected = $scope.values.filter(function(e) { return e == $scope.selectedval; });
selected.active = true;
}
And then run the addActiveProp fn in ngChange on the select.
Please give a try with below code
<select ng-model="obj.arr[otherObj.variable]" ng-change="otherObj.variable=key" ng-options="key as value for (key , value) in obj.arrs"></select>

how to set the ng-model to a property of an object

from this example
http://jsfiddle.net/qWzTb/
How can I set $scope.correctlySelected directly to the value on selecting in the select box and not to the entire object for e.g.
{ label: 'one', value: 1 },
So on selecting anything $scope.correctlySelected should be 1 or 2
and not the entire object
{ label: 'one', value: 1 }
or
{ label: 'two', value: 2 }
Here is a working fiddle: http://jsfiddle.net/AXHeA/
This is how you do it:
<select ng-model="selected"
ng-options="opt.value as opt.label for opt in options">
And then you can assign it like so:
$scope.selected = 2;
If I understand correctly, you want to be able to just set $scope.correctlySelected = 2;, right? If so then you just need to do this for the ng-options directive:
<select ng-model="correctlySelected" ng-options="opt.value as opt.label for opt in options">
Here's an updated fiddle: http://jsfiddle.net/qWzTb/89/

Working with select using AngularJS's ng-options

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.
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>
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"};
}
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>
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
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>
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>
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);
}
});
}

Categories