I'm trying to make a directive which should remove the first option of a select.
I'm using this html to generate the select box:
<select remove-whitespace ng-model="user.encryption">
<option ng-repeat="r in selectButtons" title="{{r.text}}" ng-selected="$first" value="{{r.value}}">{{r.text}}</option>
</select>
This part of code is in my controller to populate the select box in the view:
$scope.selectButtons = [
{text: "Clear-Text", value: "no_encryption"},
{text: "MD5", value: "md5_encryption"},
{text: "SHA1", value: "sha1_encryption"},
];
I'm using this as my directive:
.directive("removeWhitespace", function () {
return{
require: 'ngModel',
link: function (scope, element, attributes, ngModel) {
console.log(element.context);
}
}
});
When i do a console.log(element.context); the following context appears in my browser console:
Yet I can't seem to remove the option with value "? undefined:undefined ?"
It seems i've found a solution to my own question:
To properly populate a select box you need to use ng-options instead of ng-repeat.
I ended up using the following:
<select ng-model="selectButton"
ng-options="r.text for r in selectButtons">
</select>
This will populate a selectbox without whitespace. And the directive is not needed anymore.
The first element (? udefined:undefined ?)is there because the current value of the ng-model does not match any of the options. It will go away on its own if you do something like this in your controller:
$scope.user.encryption="no_encryption"
To answer the question fully, here is a simple directive that removes the first option of a select, though I would advise against using anything like it because it would be better to use ng-options possibly combined with a filter
angular.module('app').directive('removeFirst',function(){
return{
link:function(scope,element) {
element.find('option')[0].remove();
},
}
})
Related
The boolean value assigned to the model doesn't pre-select the corresponding option - shows first an empty option instead:
Select:
<select data-ng-model="scriptData.privacy" data-ng-options="privacyOption.value as privacyOption.label for privacyOption in privacyOptionsSelect track by privacyOption.value"></select>
Options in the controller:
$scope.privacyOptionsSelect=[
{
label:'Registered users only can view this',
value: false
},
{
label:'Anyone with the link can view this',
value: true
}
];
scriptData.privacy is set to false.
You should be careful when using the ngOptions expression with the format:
select as label for value in array
together with track by. This is because track by is applied to the value assigned to your ngModel, so if your selection is in the form privacyOption.value, the track by expression is actually applied to the value. This is the reason it doesn't successfully select the initial value.
To fix this you have two options. You can either just skip track by. This would work:
<select data-ng-model="scriptData.privacy"
data-ng-options="privacyOption.value as privacyOption.label for privacyOption
in privacyOptionsSelect"></select>
Or you could change the select as-expression to select the entire privacyOption-object. This would work as well (note the ngModel-directive changed as well):
<select data-ng-model="scriptData"
data-ng-options="privacyOption as privacyOption.label for privacyOption
in privacyOptionsSelect track by privacyOption.value"></select>
For a full (and probably better) explanation, I recommend the ngOptions documentation.
try ng-repeat instead of ng-options by applying the ng-repeat to option elements inside the select.
I am working with the bootstrap-multiselect control. I need to execute multiselect() on a select element once all of the options have been populated by Angular.
Here is the select box, bound to a $scope property, with the options generated from an array of accountInfo objects snippet from my view:
<select id="property" multiple="multiple"
ng-model="selectedProperty"
ng-options="account.property_name group by account.client_name for account in accountInfo">
</select>
So once the options exist, I need to call $("#property").multiselect() to generate the bootstrap control, but I am looking for an event to listen for that tells me when all the options have been generated.
Instead of initializing bootstrap-multiselect control in a controller, do it in a directive link() function which is called after the corresponding element is rendered.
app.directive('multiselect', function() {
return {
restrict: 'A',
link: function(scope, element) {
element.multiselect();
}
};
});
Here's a demo.
Here's some quick background info. I just upgraded to Angular 1.4. I'm using an API written in C# for my server-side calls.
A section of my page shows 2 select lists (Project & Sub-project). Both are supposed to default to "(Select a ______)", which I list as the first option of each select with "value" of 0. The appropriate ng-model variables are initialized to 0.
The actual HTML code for the select lists is being generated on the server side using string concatenation, passed to the client via $http, and inserted using a directive that calls $compile (not ideal at all, but my client has pretty much chained me to this API). Prior to the 1.4 update, everything was working nicely.
Now, my Project select list is defaulting to nothing. When I inspect the element, this is what I see...
<select ng-change="updateSubProjList()" ng-model="curProjID">
<option value="? number:0 ?"></option>
<option value="0">(Select a Project)</option>
<option value="1">First Project</option>
<option value="2">Second Project</option>
...
</select>
...with that first "? number:0 ?" entry being the one that is currently selected. My Sub-project select list still initializes just fine, which makes this even more odd.
I know that there were some updates to $compile in the update to AngularJS 1.4, but I can't figure out a solution to my problem. Any assistance would be greatly appreciated.
There seems to be a change in 1.4 related to how the selected option is matched against ngModel by comparing the value attribute in <option value="0"> - it now requires to explicitly use a string to match, rather than allowing for an integer.
In fact, the documentation clearly states:
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 using a directive ... or use ngOptions to specify the set of options.
To fix, change the initialized value of $scope.curProjID to be a string:
$scope.curProjID = "0"; // instead of $scope.curProjID = 0;
When there is no match (and there isn't, unless you assign a string "0"), select adds an "unknown" option: <option value="? number:0 ?"></option>.
There is another way: using a directive implementing a parser and a formatter. See http://plnkr.co/edit/JZPay8jgm5AXKGXDCjSb
thankx goes to narretz!
Pseudo code:
<select convert-number ng-model="x">
<option value="100">100</option>
<option value="200">200</option>
</select>
app.directive('convertNumber', function() {
return {
require: 'ngModel',
link: function(scope, el, attr, ctrl) {
ctrl.$parsers.push(function(value) {
return parseInt(value, 10);
});
ctrl.$formatters.push(function(value) {
return value.toString();
});
}
}
});
I had the same issue. Instead of making sure that my JavaScript code uses string all over the place, I did the following:
Removed the <option ...> items from the HTML file
Introduced a list into JavaScript, similar to:
var orderQuantities = [
{ id: 100, text: '100' },
{ id: 200, text: '200' },
{ id: 300, text: '300' },
];
Did make sure that orderQuantities is visible in the scope
Used ng-option in the select tag as follow:
<select ng-model="vm.entity.OrderQuantity" ng-options="orderQuantity.id as orderQuantity.text for orderQuantity in vm.orderQuantities"></select>
<select ng-model="x">
<option value="100">100</option>
<option value="200">200</option>
</select>
scope.x = '200'
or read this doc.
https://docs.angularjs.org/guide/migration
I have a select element with an ng-model and ng-options on it. ng-options makes the possible selections dynamic however I have noticed that when ng-options updates the options ng-model is not also getting updated. For example if the select element has the value of "foo" selected then the ng-options model is updated and "foo" is no longer an option the selector updates to blank but ng-model still shows "foo" as its value. I would expect that ng-model would update as well to equal null. The only thing I can think of is to add a watch on items but that seems kind of lame. What is the best way to get ng-model to stay in sync with the select element in this scenario?
<div ng-controller="MyCtrl">
<p>Selected Item: {{foo}}</p>
<p>
<label>option</label>
<select ng-model="foo" ng-options="item.val as item.label for item in items">
<option value="">Select Something</option>
</select>
</p>
<button ng-click="remove()">Remove Second Item Option</button>
<p>{{items}}</p>
</div>
Here is the jsfiddle to illustrate the issue. http://jsfiddle.net/dberringer/m2rm8Lh6/2/
Note: I'm aware I could manually update the foo value with the delete method. This method is just to illustrate the issue.
Thanks for your feedback.
Update: I fixed a typo where referred to ng-options as ng-select.
change the button like
<button ng-click="remove(2)">Remove Second Item Option</button>
change the remove function
$scope.remove = function(removeIndex) {
if($scope.foo == removeIndex) {
$scope.foo = null;
}
$scope.items.splice(removeIndex-1,1);
};
here is the Demo Fiddle
Reason is,
ng-change is not going to trigger when,
if the model is changed programmatically and not by a change to the input value, check the Doc here
so u are not changing the select value by changing the select box instead do it using a button (programmatically) so angular will not trigger the change event on the select element and then angular doesn't know model is changed ,this might be the reason.
then what u need to do is change model value manually as $scope.foo = null;
I think angular didn't check that, once the ngOptions value changes, angular didn't do a check to see if the ngModel is exists in ngOptions.
angular.js
line 21371:
self.databound = $attrs.ngModel;
line 21828 - 21840:
return function (scope, element, attr) {
var selectCtrlName = '$selectController',
parent = element.parent(),
selectCtrl = parent.data(selectCtrlName) ||
parent.parent().data(selectCtrlName); // in case we are in optgroup
if (selectCtrl && selectCtrl.databound) {
// For some reason Opera defaults to true and if not overridden this messes up the repeater.
// We don't want the view to drive the initialization of the model anyway.
element.prop('selected', false);
} else {
selectCtrl = nullSelectCtrl;
}
You can see above code checks which option should be selected when generated the options, but i can't find a reverse check when ngOptions got updated.
I am creating a new directive for the select element. As a best practice, I would like to receive some of the options from the server and one option I want to create in the client code e.g. "Search all cars".
This is an example of how I want it to look:
<select>
<option value="">Search all cars</option>
<option value="aud">Audi</option>
<option value="bmw">BMW</option>
<option value="maz">Mazda</option>
</select>
It is important that the value of "Search all cars" is empty.
But even though I have added an empty element in the select (ref other SO posts) it still gives me an unwanted option:
<option value="?" selected="selected"></option>
Here is an plunker example of the issue/bug using an Angular directive with transclude and replace.
Anyone got a suggestion of how to solve this?
I have also added an issue to the angular team here.
EDIT:
The reason why I would like to have this as an directive is that I want to decide if I should have the "Search all cars" option or not depending on where it is implemented in my application.
EDIT2:
Angular team confirms that this is a bug. "It looks like the root reason is that if an unknown option is added after the initial setup, then it will not replace the generated unknown option" - lgalfaso.
#Yoshi is right, it's to do with ngTransclude. As soon as you move the default option into the directive itself, the problem goes away. To work around this issue, since you don't care about the default value so much, you can modify the directive slightly and just import the text for the default option:
app.directive('mySelect', [function () {
return {
restrict: 'E',
replace: true,
scope: {
default: '#'
},
template: '<select ng-options="key as value for (key, value) in myMap"><option value="">{{ default }}</option></select>',
link: function postLink(scope, element) {
scope.myMap = {"bmw":"BMW","maz":"Mazda","aud":"Audi"}; // e.g. hashmap from server
}
};
And then your HTML becomes:
<my-select ng-model="myModel" ng-change="doSomething(myModel)" default="Search all cars">
</my-select>
I just had the same issue as you and unfortunately #morloch's answer did not help me.
In my case, the transclusion is dynamic.
I started with your plunker and figured out that, if we use the transclusion during the prelink, it solves the issue.
link: {
pre:function preLink(scope, element, attrs, controllers, transcludeFn) {
scope.myMap = {"bmw":"BMW","maz":"Mazda","aud":"Audi"}; // e.g. hashmap from server
transcludeFn(scope, function(clone) {
element.append(clone);
});
}
}
Here is my plunker : http://plnkr.co/edit/4V8r6iW2aKCLLbpIZc5e?p=preview