Providing id values for each option in ng-options - angularjs

I have a select box that looks like this:
<select ng-model="ctrl.arrayVal" ng-options="option for option in ctrl.myarray"></select>
where ctrl.myarray looks something like:
ctrl.myarray = [100, 200, 700, 900];
This all works great, but for automated testing purposes I need to provide an id to each of the dynamically generated options. So for example if I rewrote this using ng-repeat then I would need something like this:
<select ng-model="ctrl.arrayVal">
<option id="array-{{option}}" ng-repeat="option in ctrl.myarray" value="{{option}}">{{option}}</option>
</select>
Notice the array-{{option}} id that is possible in this second snippet using ng-repeat. I need to provide a similar id but using the first ng-options method.
Is there a way to do this?

Currently ng-options only lets you specify the value property of the <option> element using the select as or track by syntax. It is also possible to add the disabled property if your data source is an object (not an array). It is not possible to further customize the fields of the <option> element including id.
Docs on ngOptions

Maybe you can achieve your goal with a custom directive. For example:
angular.module('myApp', [])
.directive('idAdder', function() {
return {
link: function(scope, element, attrs) {
scope.$watch(attrs.ngModel, function() {
scope.$evalAsync(function() {
angular.forEach(element.find('option'), function(o, k) {
o.id = o.value;
});
});
});
}
}
});
Then, use it as a custom attribute:
<select ng-model="ctrl.arrayVal"
ng-options="option for option in ctrl.myarray"
id-adder></select>
The $watch and $evalAsyncare used to execute the function after the options have been populated/created.

Related

How to invoke a JQuery method after Angular select directive applies a model change?

I am trying to change the selection of David Stutz's bootstrap-multiselect via Angular ng-model:
<select ng-model="selection" multiple="multiple" id="my-example">
<option value="cheese">Cheese</option>
<option value="tomatoes">Tomatoes</option>
<option value="mozarella">Mozzarella</option>
<option value="mushrooms">Mushrooms</option>
<option value="pepperoni">Pepperoni</option>
<option value="onions">Onions</option>
</select>
The changes to the model only get applied to the underlying select element, but the bootstrap-multiselect doesn't get updated automatically. Looking at its documentation, this is expected: you are required to call multiselect('refresh') afterwards to propagate the changes:
$('#my-example').multiselect('refresh');
My question is:
How to invoke this method when the model changes after Angular is done updating the select element?
Since I need to access the element, I assume directives are the way to go. I was looking at decorators, which in theory I could use to modify the behavior of the built-in select directive, but I don't know how to get my code invoked at the right moment.
I've prepared a plunk to demo the issue:
I have two multiselects bound to the same model: a bootstrap-multiselect and a plain one
I initialize both with a default selection
The first button changes the selection. The plain multiselect is updated immediatelly, but the bootstrap-multiselect appears unchanged.
The second button shows the current model value in an alert.
The third button calls refresh on bootstrap-multiselect, which causes it to update. This is what I would like to get called automatically by Angular.
In the end I managed to solve my problem with a decorator. I based it on the Directive Decorator Example in AngularJS documentation.
Unlike ngHrefDirective from the example, selectDirective defines both preLink and postLink, therefore the compile override must also return both. I only needed to change postLink though, where $render is defined. In my version of the method I simply invoked the original method, which updates the select element, and called multiselect('refresh') afterwards, which was my original requirement:
app.config(['$provide', function($provide) {
$provide.decorator('selectDirective', ['$delegate', function($delegate) {
var directive = $delegate[0];
directive.compile = function() {
function post(scope, element, attrs, ctrls) {
directive.link.post.apply(this, arguments);
var ngModelController = ctrls[1];
if (ngModelController) {
originalRender = ngModelController.$render;
ngModelController.$render = function() {
originalRender();
element.multiselect('refresh');
};
}
}
return {
pre: directive.link.pre,
post: post
};
};
return $delegate;
}]);
}]);
I dont think you should mix that (why refresh data with jquery while the data is in angular's watch cycle?). You can do something like this with your options:
<option ng-value="Cheese.val">{{Cheese.text}}</option>
<option ng-value="Tomatoes.val">{{Tomatoes.text}}</option>
And handle the rest with Angular (maybe google for angular + multiselect)

Custom Directive : Dropdown selected value not binding

I'm trying to create a custom directive for a drop down control in AngularJS 1.4.4. I can handle the selected event, but i can not get the binding for what is selected in the drop down list.
I want to call this from Html markup the following way.
<my-dropdown-list source="myList" destination="mySelection" />
The angular js custom directive is here.
(function() {
var directive = function($compile) {
return {
restrict: 'E',
scope: {
model: '=source',
selectedValues: '=destination'
},
controller: function($scope) {
$scope.onSelChange = function() {
alert('called');
console.log($scope.selectedItem.Code, $scope.selectedItem.Name);
};
// $scope.selectedItem is always undefined here.
},
link: function ($scope, $elem) {
var rowHtml =
'<select ng-options="item as item.Name for item in model" ng-model="selectedItem" ng-change="onSelChange()"></select>';
$elem.html(rowHtml);
$compile($elem.contents())($scope.$new());
}
};
};
my.directive('myDropdownList', directive);
})();
I'm new to Angular, so this may be something small that i missed here, but i can't seem to get a value for 'selectedItem'
I find out this in AgularJS document
Note that the value of a select directive used without ngOptions is
always a string. When the model needs to be bound to a non-string
value, you must either explictly convert it using a directive (see
example below) or use ngOptions to specify the set of options. This is
because an option element can only be bound to string values at
present.
Link: https://docs.angularjs.org/api/ng/directive/select
You should use ngRepeat to generate the list like this post:
Angularjs: select not updating when ng-model is updated

Place 'selected' attribute in matching option (in directive)

How do I go about placing the 'selected' attribute in the option where device.types value is equivalent to. For some weird reason, the first option gets displayed even though device.type is of some other value. Where's the best place to put the code in the directive, link, compile, or controller?
template.html
<div>
<select ng-model="device.type">
<option value="value1">value1</option>
<option value="value2" selected>value2</option>
.
.
.
<option value="valueN">valueN</option>
</select>
</div>
directive
app.directive('details', function() {
return {
restrict : 'E',
scope : {
device: '=',
...
},
templateUrl : 'template.html',
link : function(scope, element, attrs) {
// put 'selected' on option element where device.type is equivalent to
// is it preferrable to put logic here? or in controller or compile
}
};
});
Appreciate your inputs. Thanks.
You don't need to create a directive to achieve that.
Just fill you select with ng-options and use track by <some field>.
Then, when your select's model changes, it will select the right option corresponding to the field you choose in track by <some field>.
Something like that :
<select
ng-model="device"
ng-options="device as device.type for device in devices track by device.type">
</select>
So, when you put a device object in the select's model, it will select the option with the device.type matching your model.

Apply kendo dropdownlist style only on angular select

I have a select which is being populated using angular binding.
<select class='clsBucket' id='optBuckets' ng-options='opt as opt.name for opt in buckets' ng-model='bucketSelected' ng-change='changeBucket()'>
Now i want to apply the Kendo dropdownlist style on this select , but i don't want to populate the options using kendo datasource etc and continue to do that using angular.
If i use $('#optBuckets').kendoDropDownList() then i get the requiired style applied but the binding data is lost.
Any help in order to resolve that is highly appreciated.
The above code lists "buckets" as the data source. With that in mind, the promise which assigns 'buckets' to the scope should have it's promise exposed on the scope. From there a directive can access it (here called 'bucketsPromise')
The code in the controller may look like such:
$scope.bucketsPromise = bucketsService.get().then(function(data) {
$scope.buckets = data;
}).promise;
The directive will the appear as such:
.directive('angularToKendoDropdown', function() {
return {
scope: {
'bindToCtrl': '&dataSourcePromise'
},
link: function(scope, element, attr) {
scope.bindToCtrl.then(function() {
$(element).kendoDropDownList();
})
}
};
});
The given select would appear as such:
<select class='clsBucket angular-to-kendo-dropdown' id='optBuckets'
ng-options='opt as opt.name for opt in buckets'
ng-model='bucketSelected' ng-change='changeBucket()'
data-source-promise='bucketsPromise'>
</select>

Angularjs ng-repeat race condition in setting dropdown value

I had the problem of getting resource data from an API, loading that into a dropdown select, and setting the selected value of the dropdown. Basically it was trying to set the value of the dropdown before it was populated. I have two different ways to do this, but was wondering if anyone had a "better" way, or a "better practice" way. Here are my two ways.
Option 1: Directive attached to ng-repeat element
Controller
$scope.users = User.query();
$scope.dude={
name: "Dude",
id: 3
}
HTML
<select id="userSelect" ng-show="users.length">
<option ng-repeat="user in users" choose dude="dude">{{user.username}}</option>
</select>
Directive
.directive('choose', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
if (scope.user) {
if (scope.user.id === scope.dude.id) {
$("#userSelect").val(element.val());
}
}
}
}
});
Option 2: Watch for the users length to change (the call is returned, and the dropdown is populated)
Controller
$scope.users = User.query();
$scope.dude={
name: "Dude",
id: 3
}
$scope.$watch('users.length', function() {
$("#userSelect").val($scope.dude.id);
});
HTML
<select id="userSelect" ng-show="users.length">
<option ng-repeat="user in users" value="{{user.id}}>{{user.username}}</option>
</select>
Any opinions on which one is better practice? Or if there is any other better way?
So, promises are your friend for this sort of thing. I'm going to use $http instead of resources, because I'm more familiar with it, but I'm pretty sure recent version of
resources return promises (or can).
Also.. no jquery in your controllers. Use directives like ng-model to change input values.
Also using ng-options to populate the options for a select is more powerful than using ng-repeat on an "option" element.
Here's what a lot of my code looks like (except that I'm using jsonp here instead of just get). http://jsfiddle.net/HB7LU/142/
CONTROLLER:
function MyCtrl($scope, $http) {
// The id we want to select when the data loads:
var desiredId = 3;
// Overly complicated $http request so that I can load with jsonp:
// You could normally use just $http.get()
$scope.users = $http.jsonp('http://www.json-generator.com/j/geku?callback=JSON_CALLBACK').then(function(d) { return d.data.result; });
// Use the returned promise to set the selection when the data loads:
// I'm using the "underscore" library function "findWhere" to set my
// model to the object I want selected:
$scope.users.then(function(d) {
$scope.uservalue = _.findWhere(d,{id:desiredId});
});
}
HTML:
<div ng-controller="MyCtrl">
{{uservalue | json}}
<select ng-model="uservalue" ng-show="users.length" ng-options="user.name for user in users">
</select>
</div>

Resources