We are facing multiple issues with select field's empty option.
My rendering code is as follows:
<select ng-switch-when="select" id="{{field.name}}" ng-model=data[field.name] >
<option ng-repeat="option in field.options" value="{{option.value}}">{{option.description}}
</select>
Here our UI is dynamically generated and options also populates dynamically using REST APIs. The Select field is bound to a data object that also populated using REST calls that might have blank value initially.
Issue
It automatically adds a empty option where it is not required and disappear after selecting any non-empty option.
Also it is showing following error for few select fields
TypeError: Cannot call method 'prop' of undefined
at selectDirective.link.ngModelCtrl.$render (lib/angular/angular.js:20165:47)
at Object.ngModelWatch (lib/angular/angular.js:16734:14)
at Scope.$get.Scope.$digest (lib/angular/angular.js:11800:40)
at Scope.$get.Scope.$apply (lib/angular/angular.js:12061:24)
at done (lib/angular/angular.js:7843:45)
at completeRequest (lib/angular/angular.js:8026:7)
at XMLHttpRequest.xhr.onreadystatechange (lib/angular/angular.js:7982:11)
I have doubt that is use following code in angular
function setupAsSingle(scope, selectElement, ngModelCtrl, selectCtrl) {
ngModelCtrl.$render = function() {
var viewValue = ngModelCtrl.$viewValue;
if (selectCtrl.hasOption(viewValue)) {
if (unknownOption.parent()) unknownOption.remove();
selectElement.val(viewValue);
if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy
} else {
if (isUndefined(viewValue) && emptyOption) {
selectElement.val('');
} else {
selectCtrl.renderUnknownOption(viewValue);
}
}
...
and it break at this line
if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy
because the empty option is provided by REST API at later stage and not available till initial rendering.
Please provide some suggestion for implementation and some alternate approach.
P.S : I can not use ng-option with select as it emit indexed values and I need actual values for some DOM manipulation.
I would first read over how to do data-ng-options (http://docs.angularjs.org/api/ng.directive:select) because your select will cause you a bit of issues. Here's how you could move your code around:
<span data-ng-switch-when="select">
<select id="{{field.name }}" data-ng-model="data[field.name]" data-ng-options="option.value as option.description for option in field.options></select>
</span>
Secondly, the reason that the blank option is selected is because data[field.name] is not set to a value that's present in option.value. You need to set it in your controller so the blank option goes away. You can see a similar question here: Why does AngularJS include an empty option in select?
Related
I have two SELECTs on a page. They both load the possible options correctly and the ng-model appears to set properly as the initial value is set based on values in the model when loaded. I want to be able to make sure that the user does not choose a value for both of the SELECTs so I added a function for the ng-click. Inside the function I check if one was initially set that the other model does not now have a value. The problem I am facing is that when I get into the function, the models don't appear to be reflecting what the user has chosen.
I looked at a lot of questions on here and found solutions that indicated that the ng-options probably needed some changes. I have tried what I have seen, but nothing seems to be helping.
Here are the two HTML SELECTs
<select ng-model="example3model" class="form-control input-md" ng-click="checkTags(3)" ng-options="industry as industry.text group by industry.group for industry in industryData track by industry.id" >
<option value="">-- Select Industry --</option>
</select>
<select ng-model="example4model" class="form-control input-md" ng-click="checkTags(4)" ng-options="product as product.text group by product.group for product in productData track by product.id" >
<option value="">-- Select Product --</option>
</select>
Both arrays (industryData and productData) look similar (different actual values) and look like this:
industrydata is an array of about 28 objects like these 3...
{group: "Energy and Natural Resources", id: "5", text: "Chemicals"}
{group: "Consumer Industries", id: "6", text: "Consumer Products"}
{group: "Public Services", id: "7", text: "Defense Security"}
If I look at the model of one of the selects if initially set with values it looks like: (this is for an 'example4model'
Object
group : "Finance"
id : "34"
text : "Governance, Risk, and Compliance"
My function snippet looks like this:
$scope.checkTags = function(curModel) {
if (curModel == 3) {
if ($scope.example4model != null && $scope.example4model.text != '' && $scope.example3model != null) {
$rootScope.showMsg("Remove Product before adding an Industry.");
$scope.example3model = null;
}
} else {
if ($scope.example3model != null && $scope.example3model.text != '' && $scope.example4model != null) {
$rootScope.showMsg("Remove Industry before adding a Product.");
$scope.example4model = null;
}
}
};
If example4model is set initially, and I try to choose a value from the example3model select, I would think when I come into the function, I should see some value in example3model, but it is null. If I change the option chosen for example4model, when I come into the function, it shows the initial value and not the newly selected one.
It seems that somehow the model is not being updated to show the chosen options but I can't figure out why. Based on responses to other questions, I added the 'as' clause and the 'track by' clauses but that didn't help either.
Any help would be appreciated.
So I drew up a sample of your provided snippets in Plunker (bottom of post)...
Have you tried initializing your models with null?
What may be causing the issue on your end (if I understand the issue correctly) is that your models will be undefined before being set using the <select>. And in your checkTags() method, you are checking for "not null".
I suggest declaring your models in your controller like so:
$scope.example3model = null;
$scope.example4model = null;
This should allow your checkTags() if statements to catch the model values before being set. Alternatively, you can change your if statements to check for whether the model is "undefined" instead.
Other Recommendations
I also might recommend changing ng-click to ng-change -- ng-click will fire your checkTags() function whenever the <select> is clicked whereas ng-change just fires it when an option is selected.
In your checkTags() if statements, you can null check with a simple if($scope.example3model) -- check my snippet for those (line 43).
Don't forget to use !== when you are checking a value and looking for a truthy / falsy (bool) evaluation.
Plunker
https://embed.plnkr.co/mUgsqY/
Wrap checkTags() in a $timeout:
// In your HTML
<select ng-model="example3model" ng-click="initiateCheckTags(3)">
<option value="">-- Select Industry --</option>
</select>
// In your Controller
$scope.initiateCheckTags = function(curModel) {
$timeout(function() {
$scope.checkTags(curModel);
}, 100);
}
I'm adding tag by selecting from list (which is populated using $http request). The tag is added but the text which I have typed that remains there with ng-invalid-tag class.
ScreenShots
1) Initially,
2) Typing 3 letters to get HTTP Call.
3) Now after selection of first Skill "Angular Js'.
4) It shows that .input.invalid-tag is enabled. And which doesn't clear the placeholder.
My Input Tag is as below.
<tags-input ng-model="employerMyCandidatesCtrl.skillList" placeholder="Skills..."
replace-spaces-with-dashes="false"
add-from-autocomplete-only="true"
display-property="skillName"
on-tag-added="employerMyCandidatesCtrl.addTagToSkillData($tag)"
on-tag-removed="employerMyCandidatesCtrl.removeTagFromSkillData($tag)">
<auto-complete
source="employerMyCandidatesCtrl.loadSkillData($query)"
displayProperty="skillName" debounce-delay="500"
min-length="3">
</auto-complete>
</tags-input>
Controller Code is as below.
vm.skillList = [];
vm.loadSkillData = function(query) {
return EmployerServices.getAllSkillsPromise(query); // $http call.
};
vm.addTagToSkillData = function(tag) {
if (_.findIndex(vm.skillList, tag) < 0) {
vm.skillList.push(tag);
}
};
vm.removeTagFromSkillData = function(tag) {
var ind = _.findIndex(vm.skillList, tag) > -1 ? vm.skillList.splice(ind, 1) : '';
};
Is any configuration mistake I'm doing?
There are 4 attributes for onTagAdding, onTagAdded, onTagRemoving, onTagRemoved so the basic difference between the attributes ending with adding compared to those ending with added is
Adding suffixed tags are expecting a boolean which when true will be added
or removed based on the tag used.
But onTagAdded/Removed already adds the tag, before the function is called hence we can do some additional logic or else strip the ng-model of the added value or add back the removed value(not very easy).
Check the below JSFiddle to see the four attributes in action here
I have made a custom service to supply the data, so the final answer to your question will be to use the appropriate attribute (onTagAdding, onTagAdded, onTagRemoving, onTagRemoved) based on your usecase. From the above code, I think we need not write onTagAdded, onTagRemoved since its done automatically.
I have a section of code that displays two different ways based on a condition. In both ways, there is a value that I want to check:
user.name
This is displayed on the page like
<span ng-show="showusername && something > 3">{{user.name}} (other stuff here)</span>
<span ng-show="showusername && something <= 3">{{user.name}}</span>
My problem is, this is used elsewhere on the page as well, and the protractor piece can't seem to find the binding if I use by.binding('user.name'), it finds multiple, and displays
Expected '' to equal 'Joe Smith'
You can filter out the visible elements only:
var visibleUserNames = element.all(by.binding("user.name")).filter(function (elm) {
return elm.isDisplayed().then(function (isDisplayed) {
return isDisplayed;
});
});
expect(visibleUserNames.count()).toEqual(1);
expect(visibleUserNames.first().getText()).toEqual("Joe Smith");
I have this html:
<select ng-model="group.newCriterionType">
<option ng-repeat="type in group.availableTypes" value="{{type}}" ng-selected="$first">{{type}}</option>
</select>
And this model code (using jresig's Class mini-library)
var FilterGroup = Class.extend({
className: 'FilterGroup',
widgetType: 'group',
init: function(name) {
this.name = name;
this.availableTypes = FilterTypes;
this.newCriterionType = this.availableTypes[0];
this.updateAvailableTypes();
},
});
However, I still get an empty element in my select. According to all the answers I've seen on SO so far, this is apparently the behaviour of when your model value contains an incorrect value. However:
I added in some console.log statements to check the value
This code is definitely being called because availableTypes is being set
It's pulling it from the array it's looping from. How can this be an incorrect value?
It persists even if i pull the ng-selected out
It persists if I use ng-options instead of looping through them (though that then means i can't set the value to the be the same as the text without turning it into an object)
Any clues much appreciated.
James
I have a drop-down list
<select ng-model="referral.organization"
ng-options="key as value for (key, value) in organizations">
</select>
where organizations is filled using a $http request. I also have a resource referral which includes several properties, including an integer organization that corresponds to the value saved in the drop-down. Currently, the drop-down works fine and selecting a new value will update my referral resource without issue.
However, when the page loads the drop-down is blank rather than displaying the value of referral.organization that was retrieved from the server (that is, when opening a previously saved referral form). I understand that this is likely due to my resource being empty when the page first loads, but how do I update the drop-down when the information has been successfully retrieved?
Edit:
{{ organizations[referral.organization] }} successfully lists the selected value if placed somewhere else on the page, but I do not know how to give the tag this expression to display.
Second Edit:
The problem was apparently a mismatch between the key used in ngOptions and the variable used in ngModel. The <select> option's were being returned as strings from WebAPI (despite beginning as Dictionary) while the referral model was returning integers. When referral.organization was placed in ngModel, Angular was not matching 2723 to "2723" and so forth.
I tried several different things, but the following works well and is the "cleanest" to me. In the callback for the $resource GET, I simply change the necessary variables to strings like so:
$scope.referral = $resource("some/resource").get(function (data) {
data.organization = String(data.organization);
...
});
Not sure if this would be considered a problem with Angular (not matching 1000 to "1000") or WebAPI (serializing Dictionary<int,String> to { "key":"value" }) but this is functional and the <select> tag is still bound to the referral resource.
For simple types you can just set $scope.referral.organization and it'll magically work:
http://jsfiddle.net/qBJK9/
<div ng-app ng-controller="MyCtrl">
<select ng-model="referral.organization" ng-options="c for c in organizations">
</select>
</div>
-
function MyCtrl($scope) {
$scope.organizations = ['a', 'b', 'c', 'd', 'e'];
$scope.referral = {
organization: 'c'
};
}
If you're using objects, it gets trickier since Angular doesn't seem smart enough to know the new object is virtually the same. Unless there's some Angular hack, the only way I see forward is to update $scope.referral.organization after it gets loaded from the server and assign it to a value from $scope.organizations like:
http://jsfiddle.net/qBJK9/2/
<div ng-app ng-controller="MyCtrl">
<select ng-model="referral.organization" ng-options="c.name for c in organizations"></select>
{{referral}}
<button ng-click="loadReferral()">load referral</button>
</div>
-
function MyCtrl($scope) {
$scope.organizations = [{name:'a'}, {name:'b'}, {name:'c'}, {name:'d'}, {name:'e'}];
$scope.referral = {
organization: $scope.organizations[2]
};
$scope.loadReferral = function () {
$scope.referral = {
other: 'parts',
of: 'referral',
organization: {name:'b'}
};
// look up the correct value
angular.forEach($scope.organizations, function(value, key) {
if ($scope.organizations[key].name === value.name) {
$scope.referral.organization = $scope.organizations[key];
return false;
}
});
};
}
You can assign referral.organization to one of objects obtained from ajax:
$scope.referral.organization = $scope.organizations[0]
I created simple demo in plunker. Button click handler changes list of objects and selects default one.
$scope.changeModel = function() {
$scope.listOfObjects = [{id: 4, name: "miss1"},
{id: 5, name: "miss2"},
{id: 6, name: "miss3"}];
$scope.selectedItem = $scope.listOfObjects[1];
};
The other answers were correct in that it usually "just works." The issue was ultimately a mismatch between the organization key (an integer) stored inside $scope.organizations and the key as stored in the $http response, which is stored in JSON and therefore as a string. Angular was not matching the string to the integer. As I mentioned in my edit to the original question, the solution at the time was to cast the $http response data to a string. I am not sure if current versions of Angular.js still behave this way.