ng-options duplicating option in select - javascript

I select element bound with a list using ng-options. I have a custom directive which adds validation directive to the select element. This custom directive compiles the select element. After compiling the select element, the options are duplicated. Is there a way to stop the duplication or clear them before compiling atleast ?
In the below code, metadata is a custom directive. In that directive, I have the compile($el)($scope) line. After executing this line, select becomes like below
Please select gender
Male
Female
Male
Female
function ($scope, $el, $attr, $ngModel) {
if (!$ngModel) {
return;
}
var elementMetadata = JSON.parse($attr.metadata);
angular.forEach(elementMetadata.validators,
function (item) {
$el.attr(item.name, item.value);
});
$el.removeAttr('metadata');
$compile($el)($scope);
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<select name="gender" id="lstGender" ng-options="gender for gender in genderList track by gender" ng-model="fields.gender" metadata="{{template.gender}}">
<option value="">Please select gender</option>
</select>

Move the compiling select element to last function of compiling to execute by using post
pre: function(scope, element) {
scope.values = [ 'male', 'female' ];
},
post: function() {
$compile(tElement)(scope);
}
codepen- https://codepen.io/nagasai/pen/MQQLbB

Related

Angular 1.5 "track by" ruines binding in ng-options

I need to select option in combobox from ajax loaded data. That data comes as list of objects. The problem is that ng-option compares objects by reference and thus setting model to objects element results in appearing new empty option in combobox instead of selecting correct option.
The known workaround is to use track by expression.
And here is example code:
var myApp = angular.module("myApp", []);
myApp.controller("myCtrl", function($scope) {
$scope.roles =[
{ key:"administrator", value:"ROLE_ADMIN" },
{ key:"operator", value:"ROLE_OPERATOR" },
];
// this emulates data from server
// won't work without 'track by'
$scope.role={ key:"administrator", value:"ROLE_ADMIN" };
});
Template:
<body ng-app="myApp" ng-controller="myCtrl">
0: <input ng-model="roles[0].key" />
<br>
1: <input ng-model="roles[1].key" />
<br>
select role: <select ng-model="role" ng-options="r.key for r in roles track by r.value">
</select>
<pre>selected role={{role|json}}</pre>
</body>
Here another problem arises. When one selects role in combobox and then
changes it's "key" property in textbox, then selected role stays unchanged. So it looks like binding is suddenly gets broken.
https://jsfiddle.net/xLqackxw/8/
from Angular documentation https://docs.angularjs.org/api/ng/directive/ngOptions
<select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
So:
<select ng-model="role" ng-options="r as r.key for r in roles track by r.value"></select>
'$scope.role' value will be object like { key:"administrator", value:"ROLE_ADMIN" } or { key:"operator", value:"ROLE_OPERATOR" }

AngularJS case-insensitive binding to a dynamic select dropdown without changing the ng-bind value to lower or upper case

I am trying to perform a case-insensitive bind of an ng-model to a dynamic select drop-down using AngularJS.
by going through various other relavent answers from stack over flow, i have come up with something like below on the view , here caseinsensitive-options is an directive which i have come up with referencing the following solution
AngularJS case-insensitive binding to a static select drop-down
HTML:
<select id="dcName" caseinsensitive-options="" ng-model="DC.name" class="form-control">
<option value="">--- Please Select ---</option>
<option ng-repeat="dataCenter in inventoryDataCenters" value="{{dataCenter}}">{{dataCenter}}</option>
</select>
js directive code :
app.directive('caseinsensitiveOptions', function() {
return {
restrict: 'A',
require: ['ngModel', 'select'],
link: function(scope, el, attrs, ctrls) {
var ngModel = ctrls[0];
ngModel.$formatters.push(function(value) {
var option = [].filter.call(el.children(), function(option) {
return option.value.toUpperCase() === value.toUpperCase()
})[0];
return option ? option.value : value
});
}
}
});
The expected result is
when i pass something like this for
$scope.inventoryDataCenters = ["TEST1","test2",teST3]; and ng-model for DC.name has value TesT1.
The drop down should show TEST1 by doing case insensitive binding. That doesnt happen now. The above solution works perfect when we have static drop down.
things to be considered is that the select is inside a div which ng-repeat as shown below
ng-repeat="DC in workflowData.project_data.service_info.variables.service_data['data-center']"
and ng-model for select DC.name is derived from the above array DC.
check this URL for binding based on case insensitive value
https://jsfiddle.net/dwd2du17/6/
<div ng-app="module" ng-controller="controller as ctrl">
<select id="animal" ng-model="ctrl.animal">
<option value="">--- Select ---</option>
<option value="CaT">Cat</option>
<option value="DOg">Dog</option>
</select>
{{ctrl.animal}}
</div>
var appForm = angular.module('module', [])
.controller('controller', function($scope) {
});

How to apply custom angular directives dynamically?

I am exploring advanced features in AngularJS such as custom directives. I want to have a textbox which by using custom directive should allow either numbers only or text only depending upon the value chosen by the user from a dropdown box. I have managed to create a custom directive which allows numbers only based upon AngularJs Custom Directive fails the text box validation . I am not sure how to apply a custom directive dynamically to the same textbox. I have create another custom directive which allows text only, but not sure how to replace the number only directive with the text only directive dynamically.
<body>
<div ng-app="TextboxExample" ng-controller="ExampleController">
<form name="shippingForm" novalidate>
<select id="textBoxOptions" >
<option value="number" selected="selected">Numbers Only</option>
<option value="text">Text Only</option>
<option value="special">Special Characters</option>
<option value="any">Any Value</option>
</select>
<input id="customTextBox" unbindable number-only-input type="text" name="name" ng-model="testvalue.number" required />
<span class="error" ng-show="shippingForm.name.$error.required">
Please enter a value
</span>
</form>
</div>
<script src="scripts/angular.js"></script>
<script src="scripts/jquery.min.js"></script>
<script>
$("select").change(function () {
var selected = $("#textBoxOptions").val();
$('#customTextBox').attr("ng-model", selected);
});
</script>
<script>
angular.module('TextboxExample', [])
.controller('ExampleController', ['$scope', function ($scope) {
$scope.testvalue = { number: 1, validity: true };
}])
.directive('numberOnlyInput', ['$compile', function ($compile) {
return {
link: function (scope, element, attrs) {
var watchMe = attrs["ngModel"];
scope.$watch(watchMe, function (newValue, oldValue) {
if (newValue == undefined || newValue == null || newValue == "") {
return;
} else if (isNaN(newValue)) {
var myVal = watchMe.split(".");
switch (myVal.length) {
case 1:
scope[myVal[0]] = oldValue;
break;
case 2:
scope[myVal[0]][myVal[1]] = oldValue;
}
}
$compile(element)(scope);
});
}
};
}]);
</script>
When I change the value in the dropdown box, it correctly changes the ng-model on customTextBox as checked by inspect element. However, I am not sure how to create and apply multiple directives. Thanks
There are several possibilities. You can switch directive atttibute or whole elements:
<input {{ yourmode ? number-directive : text-directive }} ng-model="data">
or
<input ng-show="yourmode" number-directive ng-model="data">
<input ng-show="!yourmode" text-directive ng-model="data">
or you change the mode with dynamic directive attributes
<input directive-data="yourmode" my-input-directive ng-model="data">

AngularJS not binding to numeric value in select list

I have an existing select tag with existing option tags, one marked "selected", for example:
<div class="form-group">
<label class="control-label">Test</label>
<select ng-model="vm.testNumber">
<option>Choose...</option>
<option value="1">one</option>
<option value="2">two</option>
<option value="3" selected>three</option>
</select>
<p class="help-block">{{vm.testNumber}}</p>
</div>
Angular is binding to the model correctly and spits out "3" in the help block when the page loads, but option 3 is not selected. It binds and shows 3 as selected if I use text for the value attributes on each option, but not when it's a numerical value. Instead it inserts an option at the top of the select list:
<option value="? number:3 ?"></option>
What am I doing wrong?
From Angular DOC
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 explicitly convert it or use ngOptions to specify the set of options. This is because an option element can only be bound to string values at present.
If you change your code as like as below code snippet, you don't need to do any explicit string conversions.
<select ng-model="vm.testNumber" ng-options="item.value as item.label for item in [{label: 'one', value: 1}, {label: 'two', value: 2}]">
<option value="">Choose...</option>
</select>
Turns out you need to add a directive to make this work correctly. #codeninja.sj's answer ends up replacing 'one' with 1, 'two' with 2, etc. In the documention, the last example has this directive, convert-to-number
angular.module('app').directive('convertToNumber', function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
ngModel.$parsers.push(function (val) {
return parseInt(val, 10);
});
ngModel.$formatters.push(function (val) {
return '' + val;
});
}
};
});
None of the other examples in the documentation worked for me.
Update #codeninja.sj's updated answer does work, however, figure this directive will cut down on magic strings in my code.

ng-change detect if ng-model is null or invalid in select box and select first valid option

I have hundreds of cases like this (so the fix would have to be global and not tied to just this particular example)
There is a lot of select boxes like this:
<select ng-model="selectedItem">
<option ng-repeat="item in items | filter:attributes" value="{{item.id}}">{{item.name}}</option>
</select>
The selectedItem variable is null (and it will always initialize as null, which can't be changed in the controller in this particular situation).
What I'm trying to figure out is a way to globally watch all <select> elements in a view, see if the ng-model variable for that <select> box is null and if it is null set it to the first valid option in that select box, anytime the scope changes it will need to check if the ng-model is null and auto-select the first valid option.
The key thing to realise with this is that you can define more than one Angular directive with the same name, and all of them will be run for matching elements.
This is very powerful, as it enables you to extend functionality of built-in directives, or third party ones, etc.
Using this, I was able to create a select directive that would select the first valid option in the list whenever the model value is null.
One thing it doesn't do, however, is cope if you remove the selected item from the list (it goes back to being blank). But hopefully it is enough to get you started.
var app = angular.module('stackoverflow', []);
app.controller('MainCtrl', function($scope) {
$scope.selectedItem = null;
$scope.items = [1, 2, 3, 4].map(function(id) {
return {
id: id,
visible: true,
text: 'Item ' + id
};
});
});
app.directive('select', function() {
return {
restrict: 'E',
require: '?ngModel',
link: function($scope, $elem, $attrs, ngModel) {
// don't do anything for selects without ng-model attribute
if (!ngModel) return;
// also allow specifying a special "no-default" attribute to opt out of this behaviour
if ($attrs.hasOwnProperty('noDefault')) return;
// watch the model value for null
var deregWatch = $scope.$watch(function() {
return ngModel.$modelValue;
}, function(modelValue) {
if (modelValue === null) {
// delay to allow the expressions to be interpolated correctly
setTimeout(function() {
// find the first option with valid text
var $options = $elem.find('option'),
$firstValidOption, optionText;
for (var i = 0, len = $options.length; i < len; i++) {
optionText = $options.eq(i).text();
if (optionText !== '' && !optionText.match(/^(\?|{)/)) {
$firstValidOption = $options.eq(i);
break;
}
}
if ($firstValidOption) {
$firstValidOption.prop('selected', true);
ngModel.$setViewValue($firstValidOption.attr('value'));
// trigger a digest so Angular sees the change
$scope.$evalAsync();
}
}, 0);
}
});
// clean up in destroy method to prevent any memory leaks
var deregDestroy = $scope.$on('$destroy', function() {
deregWatch();
deregDestroy();
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<div ng-app="stackoverflow">
<div ng-controller="MainCtrl">
<select ng-model="selectedItem">
<option ng-repeat="item in items | filter:{visible:true} track by item.id" value="{{item.id}}">{{item.text}}</option>
</select>
<p>Visible items:</p>
<ul>
<li ng-repeat="item in items track by item.id">
<label>
<input type="checkbox" ng-model="item.visible">{{item.text}}
</label>
</li>
</ul>
</div>
</div>

Categories

Resources