AngularJS Select with ng-options binding to object not property - javascript

I am trying to bind a select list to ng-model state abbreviation value.. for instance "AK" for Alaska. The initial selection doesn't work apparently because the ng-model is set to a string instead of an object. I've looked all over and there are all other people that have this issue but I haven't found a solution that works.
my controller has the following code
$scope.states =
[
{
"name": "Alabama",
"abbreviation": "AL"
},
{
"name": "Alaska",
"abbreviation": "AK"
},
{
"name": "Arizona",
"abbreviation": "AZ"
}];
//I want the model to be the abbreviation string
$scope.state = "AK";
Here is the markup
<select class="form-control m-b" data-placeholder="Select Location"
required="" ng-model="state"
ng-options="state as state.name for state in states track by state.abbreviation">
</select>
<p>Selected State {{state}}!</p>
Here is a plnkr that shows shat I'm talking about
https://plnkr.co/edit/IzDY4GrOxJSaMV2MMP30

You can use
$scope.state.abbreviation
to get the abrevation of the state object that's selected.
plkr solution
OR
you could change the ng-options to
ng-options="state.abbreviation as state.name for state in states track by state.abbreviation"

Related

how to get selected value on button click in angularjs?

How to get selected value in angulajs when clicked on the button, im using the following code please suggest me?
<div class="form-inline">
<div class="form-group">
<select class="form-control" data-ng-model="selectedTimeZone">
<option data-ng-repeat-start="(key, value) in timeZoneData.countries" data-ng-bind="value.name"></option>
<option data-ng-repeat-end="" data-ng-repeat="tz in value.timezones" data-ng-bind="' - ' + tz"></option>
</select>
</div>
<div class="form-group">
<input id="btnAddTimeZone" type="button" value="Add Time Zone" class="btn btn-default" data-ng-click="populateTimeZone(selectedTimeZone)"/>
</div>
</div>
in Controller--
$scope.populateTimeZone = function (world_timezones) {
};
Json data--
{
"countries": {
"US": {
"id": "US",
"name": "United States",
"timezones": [
"America/New_York",
"America/Detroit",
]
},
"CA": {
"id": "CA",
"name": "Canada",
"timezones": [
"America/St_Johns",
"America/Halifax",
]
},
"IN": {
"id": "IN",
"name": "India",
"timezones": [
"Asia/Kolkata"
]
},
}
}
But im getting empty string.
From the AngularJS docs:
To bind the model to a non-string value, you can use one of the following strategies:
the ngOptions directive (select)
the ngValue directive, which allows arbitrary expressions to be option values (Example)
model $parsers / $formatters to convert the string value (Example)
https://docs.angularjs.org/api/ng/directive/select
Option 1: Add ng-value
All you need to do is add an ng-value to your options and it should work. You also might want to add a ng-disabled="true" to your group headers so that it prevents the user from selecting "India" and not an actually timezone.
<option ng-repeat-start="(key, value) in timezones.countries" ng-bind="value.name" ng-disabled="true"></option>
<option ng-repeat-end="" ng-repeat="tz in value.timezones" ng-bind="' - ' + tz" ng-value="tz"></option>
Plunkr: https://next.plnkr.co/edit/l5H87H8k7Af5XIqH?open=lib%2Fscript.js&deferRun=1
Option 2: ng-options with group by
Here's a possible solution using ng-options on the select. You can still achieve a groupBy functionality grouping your timezones by country name.
HTML
<form>
<label>Timezone: </label>
<select class="form-control" ng-model="selectedTimezone" ng-options="tz.timezone group by tz.country for tz in timezones"></select>
</form>
<button class="btn btn-secondary" ng-click="populateTimeZone()">Add Timezone</button>
JavaScript
function MainCtrl($scope, MainService) {
$scope.selectedTimezone = undefined;
$scope.timezones = [];
MainService.loadTimezones().then(function(timezoneData){
$scope.timezones = Object.values(timezoneData.countries).flatMap(c => {
return c.timezones.map(tz => {
return {id: c.id, name: c.name, timezone: tz};
});
});
});
$scope.populateTimeZone = function(){
console.log('selectedTimezone', $scope.selectedTimezone);
};
}
Plunkr: https://next.plnkr.co/edit/Y4AVNU9X6MUAzckz?open=lib%2Fscript.js&deferRun=1

Dropdown binding issue with track by

I am getting problem while binding my dropdown value with associative array.
Problem is with track by so like when I don't add track by to my dropdown then I have my binding with dropdown and when I add track by then O am unable to auto select dropdown value.
I want to use track by with ng-options so that angular js doesn't add
$$hashKey and leverage performance benefit associated with track by.
I am not getting why this behaviour is happening.
Note: I only want to bind name of choices like Pizza or burger for each of my $scope.items and not whole object.
Update: As I understand and with so much trying with current data structure of my $scope.items it is not working with ng-options and I want to use ng-options with track by to avoid generating hash key by Angular js. I also tried ng-change as suggested by #MarcinMalinowski but I am getting key as undefined.
So what should be my data structure of $scope.items so that when I need to access any item from my $scope.items? I can access it without doing loop (like we access items from associative array) like how I can access now with correct data structure and using ngoptions only with track by.
var app = angular.module("myApp", []);
app.controller("MyController", function($scope) {
$scope.items = [
{
"title": "1",
"myChoice" :"",
"choices": {
"pizza": {
"type": 1,
"arg": "abc",
"$$hashKey": "object:417"
},
"burger": {
"type": 1,
"arg": "pqr",
"$$hashKey": "object:418"
}
}
},
{
"title": "2",
"myChoice" :"",
"choices": {
"pizza": {
"type": 1,
"arg": "abc",
"$$hashKey": "object:417"
},
"burger": {
"type": 1,
"arg": "pqr",
"$$hashKey": "object:418"
}
}
}
];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<ul ng-app="myApp" ng-controller="MyController">
<div ng-repeat="data in items">
<div>{{data.title}}
</div>
<select ng-model="data.myChoice"
ng-options="key as key for (key , value) in data.choices track by $index"><option value="">Select Connection</option></select>
</div>
</ul>
The problems in your code are:
1) track by $index is not supported by ngOptions, it will result the value of the option to be undefined(in your case it will be an $index of ngRepeat);
2) track by doesn't work well with object data-sources (it is supposed to be used with array data-sources), from the docs:
trackexpr: Used when working with an array of objects. The result of
this expression will be used to identify the objects in the array.
Of course, you can use ngRepeat to generate option elements, but personally, I would prefer using ngOptions without track by due to the benefits it has over ngRepeat.
UPDATE: Here is the code that illustrates how you can change your initial data-source and use track by to pre-select an option in case the model is an object. But even in the first example console.log() shows that $$hashKey was not added to choices object.
var app = angular.module("myApp", []);
app.controller("MyController", ['$scope', '$timeout', function($scope, $timeout) {
$scope.items = [
{
"title": "1",
"myChoice" :"burger",
"choices": {
"pizza": {
"type": 1,
"arg": "abc"
},
"burger": {
"type": 1,
"arg": "pqr"
}
}
},
{
"title": "2",
"myChoice" :"",
"choices": {
"pizza": {
"type": 1,
"arg": "abc"
},
"burger": {
"type": 1,
"arg": "pqr"
}
}
}
];
$scope.itemsTransformed = angular.copy($scope.items).map(function(item){
delete item.myChoice;
item.choices = Object.keys(item.choices).map(function(choice){
item.choices[choice].name = choice;
return item.choices[choice];
});
return item;
});
//select an option like an object, not a string
$scope.itemsTransformed[1].myChoice = $scope.itemsTransformed[1].choices[0];
$timeout(function() {
//changes a prop in opts array - options are not-re-rendered in the DOM
//the same option is still selected
$scope.itemsTransformed[1].choices[0].arg = "xyz";
}, 3000);
$scope.selectionChanged =function(key, items){
console.log(items); //as we can see $$hashKey wasn't added to choices props
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<ul ng-app="myApp" ng-controller="MyController">
<p>Without track by:</p>
<div ng-repeat="data in items track by data.title">
<div>{{data.title}} - {{data.myChoice}}</div>
<select ng-model="data.myChoice"
ng-options="key as key for (key , value) in data.choices"
ng-change="selectionChanged(key, items)">
<option value="">Select Connection</option>
</select>
</div>
<hr/>
<p>Using track by name to pre-select an option:</p>
<div ng-repeat="data in itemsTransformed track by data.title">
<div>{{data.title}} - {{data.myChoice}}</div>
<select ng-model="data.myChoice"
ng-options="choice as choice.name for choice in data.choices track by choice.name"
ng-change="selectionChanged(key, itemsTransformed)">
<option value="">Select Connection</option>
</select>
</div>
</ul>
UPDATE 2: A simple example that shows us the fact $$hashKey property is not added to the objects when using ngOptions without track by:
var app = angular.module("myApp", []);
app.controller("MyController", ['$scope', '$timeout', function ($scope, $timeout) {
$scope.items = {
"pizza": {
"type": 1,
"arg": "abc"
},
"burger": {
"type": 1,
"arg": "pqr"
}
};
$scope.selectionChanged = function (key, items) {
console.log($scope.items);
};
}]);
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyController">
<hr/>
<p>Example without track by:</p>
<select ng-model="myChoice"
ng-options="key as key for (key , value) in items"
ng-change="selectionChanged(myChoice, items)">
<option value="">Select Connection</option>
</select>
<hr/>
{{myChoice}}
</div>
UPDATE 3: Final result below (that work with angularjs versions < 1.4, for 1.4+ I would recommend changing the data structure as $scope.itemsTransformed in the first code snippet):
angular.module("myApp", [])
.controller("MyController", ['$scope', function ($scope) {
$scope.items = [
{
"title": "1",
"myChoice": "burger",
"choices": {
"pizza": {
"type": 1,
"arg": "abc"
},
"burger": {
"type": 1,
"arg": "pqr"
}
}
},
{
"title": "2",
"myChoice": "",
"choices": {
"pizza": {
"type": 1,
"arg": "abc"
},
"burger": {
"type": 1,
"arg": "pqr"
}
}
}
];
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyController">
<div ng-repeat="data in items track by data.title">
<div>{{data.title}} {{data.myChoice}}</div>
<select ng-model="data.myChoice"
ng-options="key as key for (key , value) in data.choices">
<option value="">Select Connection</option>
</select>
</div>
</div>
ngOptions doesn't create new scope like ngRepeat directive per item therefore you don't need to take care about to get rid of $$hashKey
I would use ng-repeat to iterate on <option> (suppose you don't create long lists):
<select ng-model="data.myChoice">
<option value="">Select Connection</option>
<option ng-repeat="(key , value) in data.choices track by key" ng-value="key" title="{{key}}"
>{{key}}</option>
</select>
Working Demo Fiddle
Take look on this issue: github.com/angular/angular.js/issues/6564 - ng-options track by and select as are not compatible
I believe this issue still exists so suggest you to use ngRepeat with track by instead. For small list there is no performance penalty
ngOptions attribute can be used to dynamically generate a list of elements for the element using the array or object
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.
1.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]
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.
For Example :
$scope.items = [
{
"title": "1",
"myChoice" :"",
"choices": {
"pizza": {
"type": 1,
"arg": "abc"
},
"burger": {
"type": 1,
"arg": "pqr"
}
}
},
{
"title": "2",
"myChoice" :"",
"choices": {
"pizza": {
"type": 1,
"arg": "abc"
},
"burger": {
"type": 1,
"arg": "pqr"
}
}
}
];
From the above 2nd point, track the identity of the item not by reference.
Add keyName of key in the object and track by keyName or track by arg , type.
Track by arg or type :
<select ng-model="data.myChoice"
ng-options="choice as choice.arg for choice in data.choices track by choice.arg">
<option value="">Select Connection</option>
</select>
Or add keyName inside the choice object
$scope.items = $scope.items.filter(function(item){
delete item.myChoice;
item.choices = Object.keys(item.choices).map(function(choice){
item.choices[choice].keyName = choice;
return item.choices[choice];
});
return item;
});
HTML Code:
<div ng-controller="MyCtrl">
<ul>
<div ng-repeat="data in items">
<select ng-model="data.selected"
ng-options="choice as choice.keyName for choice in data.choices track by choice.keyName"
ng-change="selection(data.selected)">
<option value="">Select</option>
</select>
</div>
</ul>
</div>
Demo Link Example
You need to add ng-change and pass/use your ng-model value there to get any property you wish.
<select class="form-control pickupaddress ng-pristine ng-valid ng-touched m-r-sm m-t-n-xs" ng-model="item.pickup_address" tabindex="0" aria-invalid="false" ng-options="add._id as add.nick_name for add in addPerFood[item.food._id] | unique:'nick_name'" ng-change="dropDownSelect(item.pickup_address,allCarts,item,$index)">

How to make linked(dynamic?) select fields in oracle jet?

Im very new to JS and OJET. I'm using oracle jet to create a form. I need to create two select fields, the firts displays a client's name and the next one must change is values with the selected client's team members.
I have a JSON File with this format:
{
"clients": [
{
"id": "C01",
"name": "Client 1",
"manager": "Manager 1",
"team": [
{
"id": "C1MEM1",
"name": "member 1"
},
{
"id": "C1MEM2",
"name": "member 2"
},
{
"id": "C1MEM3",
"name": "member 3"
},
{
"id": "C1MEM4",
"name": "Member 4"
}
]
},
{
"id": "C02",
"name": "Client 2",
"manager": "Manager 2",
"team": [
{
"id": "C2MEM1",
"name": "member 1"
},
{
"id": "C2MEM2",
"name": "member 2"
},
{
"id": "C2MEM3",
"name": "member 3"
},
{
"id": "C2MEM4",
"name": "member 4"
}
]
}
I managed to create a select field with the clients name:
self.clientsListVal = ko.observableArray(['C01']);
self.clientsList = ko.observableArray();
$.getJSON("http://localhost:8000/js/json/clients.json").
then(function(data){
$.each(data["clients"],function(){
self.clientsList.push({
value: this.id,
label: this.name
});
});
});
Then I tried to get the next select fields this way, but it doesn't work :( :
self.memberList = ko.observableArray();
$.getJSON("http://localhost:8000/js/json/clients.json").
then(function(data){
$.each(data["clients"],function(){
if (this.id === self.clientsListVal ) {
$.each(this["team"], function(){
self.memberList.push({
value: this.id,
label: this.name
});
});
}
});
});
This is the HTML im using:
<div class="oj-applayout-content">
<div role="main" class="oj-hybrid-applayout-content">
<div class="oj-hybrid-padding">
<h3>Dashboard Content Area</h3>
<div>
<label for="clients">Clients</label>
<select id="clients"
data-bind="ojComponent:
{component: 'ojSelect',
options: clientsList,
value: clientsListVal,
rootAttributes: {style:'max-width:20em'}}">
</select>
<label for="select-value">Current selected value is</label>
<span id="select-value" data-bind="text: clientsListVal"></span>
<label for="members">Members</label>
<select id="members"
data-bind="ojComponent: {component: 'ojSelect',
options: memberList,
value: memberListVal,
rootAttributes: {style:'max-width:20em'}}">
</select>
</div>
</div>
</div>
Any help or hint? thank you!.
EDIT:
I think the problem is that self.clientsListVal is returning a function not the current selected value. I added console.log(self.clientsListVal) to the view model to see the current value.
If I change self.clientsListVal for a string:
if(this.id === 'C01'){}
I get the members of the client "C01".
I tried changing self.clientsListVal to $('#clients').val(), this is the id of the select input and i get undefined in the console.log.
How can I get the select field string value inside the viewmodel?
In Knockout, observables are functions -- so when you ask for the observable directly, like self.clientsListVal, you get the function definition. To get the underlying value, call the observable like a function: self.clientsListVal().
So your test becomes if (this.id === self.clientsListVal() ) {
Now you have another problem -- the observable holds an array, not an ID. The array may have a single ID element in it, but you have to reach into the array to get it.
Since you didn't show us how a value gets into clientsListVal, it's hard to say what you need to do. Is it bound to an input field where the user specifies a value? Is it populated from a data call? either way, do you ever need to have more than one ID in clientsListVal? If you only need to hold one ID at a time, change clientsListVal from an observableArray to a simple observable and your test will work.
If clientsListVal can hold multiple values, you'll need to loop over them. There are various ways to do this. You can get the underlying array by assigning the value of the observableArray to a variable: var clients = clientsListVal(). clients now holds the array, and you can use jQuery's $.each, the native Array.each, or some other way to loop over or map the array. Or you can use Knockout's built-in array utilities, like arrayForEach
if you don't want to change to a regular observable but expect the array to only have a single element, you can get at it like clientsListVal()[0] -- that's the 0th (first) element of the array. Watch out for empty arrays, tho.

Angular ngRepeat multiple select fields without displaying already selected data through ng-options

So the overview of the problem; I am retrieving data from an api and creating a CRUD page for it. The data has a set of labels that the user can select.
Below is a code sample representing my problem. The labels selected by the user are represented by the user.labels relationship and the total available labels that can be selected are represented by user.parent.grandparent.labels.
I'm able to sync the selection. What I can't seem to figure out is how to get rid of options that have already been selected from the list of options on any other subsequent select field.
angular.module('app', [])
.controller('select', ['$scope', '$filter', '$location',
function($scope, $filter, $location) {
$scope.user = {
"parent": {
"grandparent": {
"labels": [{
"id": 28,
"name": "Label 1",
}, {
"id": 17,
"name": "Label 2",
}, {
"id": 39,
"name": "Label 3",
}, {
"id": 77,
"name": "Label 4"
}, {
"id": 100,
"name": "Label 5"
}]
}
},
"labels": [{
"id": 28,
"name": "Label 1",
"meta": {
"score": 3
}
}, {
"id": 17,
"name": "Label 2",
"meta": {
"score": 5
}
}]
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="select">
<div ng-repeat="label in user.labels track by $index">
<div class="form-field">
<span>Label</span>
<select ng-model="user.labels[$index]" ng-options="department.name for department
in user.parent.grandparent.labels track by department.id">
</select>
</div>
<div>
<span>Score</span>
<select ng-model="label.meta.score">
<option value="1">1 (lowest)</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5 (highest)</option>
</select>
</div>
</div>
<button ng-click="user.labels.push({})">Add Label</button>
</div>
You can use a filter function inside the ng-repeat to achieve this, here is a sample Codepen showing you how:
http://codepen.io/anon/pen/ZYveOo
You need to pass the filter in the repeat definition:
<select ng-model="user.labels[$index]" ng-options="department.name for department in user.parent.grandparent.labels | filter:removeSelected track by department.id ">
Which refers to this function on scope:
$scope.removeSelected = function(val){
return !_.find($scope.user.labels, function(label) {
return label.id === val.id;
});
};
Even then though I think you are missing one use case which is that you want to be able to have the currently selected label included in the options, by removing all selected options you are removing that ability.
Updated:
Ok then, so after giving this some thought I have come up with the following filter which could be optimised but does seem to work as expected:
.filter('duplicatesFilter', function() {
return function(items, index, selected) {
var res = [selected[index]];
_.forEach(items, function(item){
if(!_.find(selected, function(label) {
return label.id === item.id;
})){
res.push(item);
}
});
return res;
};
})
Use it like so:
<select ng-model="user.labels[$index]" ng-options="department.name for department in user.parent.grandparent.labels | duplicatesFilter:$index:user.labels track by department.id "></select>
This is something I have hit a few times and each time I've worked around it. I'll take a look later if I can find a custom filter that better solves the problem and if I can't I'll tidy up this code and release one; however this should be good to go for your use-case.
Working code-pen:
http://codepen.io/anon/pen/ZYveOo

Cascading dropdowns with AngularJs with Select Distinct

I have a page on which I output a list of Cars. The results in this list are coming from a Json file.
This is an example of my Json:
[
{
"id": "1590",
"brand": "Peugeot",
"type": "508"
},
{
"id": "1591",
"brand": "Peugeot",
"type": "308"
},
{
"id": "1594",
"brand": "Honda",
"type": "Civic"
},
{
"id": "1605",
"brand": "Renault",
"type": "Clio"
},
{
"id": "1607",
"brand": "Renault",
"type": "Laguna"
}
]
I need to filter the results by e.g. Brand and Type. So I have two DropDown lists named 'selectedBrand' and 'selectedType'.
I used this markup to load the single Brands into the DropDown list:
<select id="brands" data-ng-model="selectedBrand" data-ng-options="car.brand for car in cars | unique:'brand' | orderBy:'brand'">
</select>
This works okay and gives me a DropDown list with the unique brands, like a DISTINCT SELECT.
Now I want the second DropDown list to be filtered when the first one has a selected value.
This is the markup I have for the second DropDown list:
<select id="type" data-ng-model="selectedType" data-ng-options="car.type for car in cars | filter:{brand:selectedBrand} | unique:'type' | orderBy:'type'">
</select>
But this is not working. It shows the complete list with types of every brand.
I guess I need some way to trigger it when the first DropDown list changes?
I pretty new to AngularJs, so any help would be appreciated.
I don't know if this is the best way to get those DISTINCT values out of the Json results. I only have this complete Json result set with all the data and that is what I have to work with.
Following the docs you'll need a ng-model-options="{ updateOn: 'your option here' }" to do this bind

Categories

Resources