In Angular, you can specify a placeholder value for a <select> by doing something like so:
<select ng-options="o.name for o in options" ng-model="model">
<option value="">Please choose</option>
</select>
This still works, however even when you choose an option in the list, the 'Please choose' placeholder still shows. To get around this, you could use an ng-if on the placeholder <option> to hide the option if a model value was selected, like so:
<select ng-options="o.name for o in options" ng-model="model">
<option ng-if="!model" value="">Please choose</option>
</select>
However this no longer works in Angular 1.4, the placeholder no longer shows and the entire list is cleared if you choose one of the other options.
I have created a plunkr to demonstrate this, see here: http://plnkr.co/edit/NDXZLkGavwBU3Qg5YAtT
Seems to break in 1.4.0-beta.6
This works in Firefox 38 & Chrome 43, but not IE 11
<option ng-hide="model" value="">Please choose</option>
This works in all three browsers above. Doesn't look great in IE but it works.
<option disabled value="">Please choose</option>
Thanks, I guess i'll use the disabled attribute for now. Is this considered a regression? I guess it has something to do with the refactoring around select and ngOptions, and ngIf creating a new scope
Actually, being able to use ng-if was very handy for the use-case when you could have a custom directive that contains a <select>, and then optionally have a placeholder element if the user specifies a "placeholder" attribute.
With this, the placeholder element always has to be present, and if the user doesnt specify some text for it then it just shows as a blank item in the list.
I guess I could try and remove it manually through DOM manipulation in the compile phase, but it would be nicer to be able to take care of it in the template like I used to be able to.
Actually, this last worked correctly in 1.3.16
Since 1.4.0-beta.0 it has worked but with type errors "element.setAttribute is not a function".
This was due to ngOptions executing emptyOption.attr('selected', true) while emptyOption was an ngIf comment node [ "<!-- ngIf: !name -->" ].
Since commit https://github.com/angular/angular.js/pull/11275 in 1.4.0-beta.6, JQLite no longer attempts to set attributes on comment nodes, which is in line with JQuery (according to comments in https://github.com/angular/angular.js/issues/11038). The side effect of this commit is that this use case has stopped working altogether.
Actually, this currently works if you use an ng-show instead of an ng-if. Once an option is selected from the dropdown, the placeholder element no longer shows up on the list. This is with 1.4.2
<select ng-options="o.name for o in options" ng-model="model">
<option ng-show="!model" value="">Please choose</option>
</select>
Angular 1.4.3 also suffers from this problem, and changing ng-if to ng-show is a viable workaround.
This doesn't seem to be completely fixed. There is still a problem under certain ciscumstances (e.g. when the values are already available when compiling/linking the select/ngOptions, albeit a different problem: The empty value appears fine, but the options appear twice.
The cause is (kind of) the same though: We are not properly _skipping empty and unknown options_ in the presence of an ngIf'ed empty option.
Assuming the following select:
<select ng-model="selected" ng-options="item for item in items">
<option value="" ng-if="hasEmpty">--- Select ---</option>
</select>
When hasEmpty == false is will look like this:
<select ng-model="selected" ng-options="item for item in items">
<!-- ngIf: hasEmpty -->
</select>
But when hasEmpty == true it will look like this:
<select ng-model="selected" ng-options="item for item in items">
<!-- ngIf: hasEmpty -->
<option value="" ng-if="hasEmpty">--- Select ---</option>
<!-- end ngIf: hasEmpty -->
</select>
Thus, the logic we have in skipEmptyAndUnknownOptions() isn't adequate:
function skipEmptyAndUnknownOptions(current) {
...
while (current &&
(current === emptyOption_ ||
current === unknownOption_ ||
emptyOption_ && emptyOption_.nodeType === NODE_TYPE_COMMENT)) {
...
}
...because emptyOption_.nodeType === NODE_TYPE_COMMENT will always evaluate to true (emptyOption_ will be <!-- ngIf: hasEmpty --> regardless of the value of hasEmpty).
For good meassure, here is a failing testcase:
it('should be possible to use ngIf in the blank option when values are available upon linking',
function() {
var options;
scope.values = [{name: 'A'}]; // Moving this AFTER creating the <select> "solves" the issue
createSingleSelect('<option ng-if="isBlank" value="">blank</option>');
scope.$apply('isBlank = true');
options = element.find('option');
expect(options.length).toBe(2);
expect(options.eq(0).val()).toBe('');
expect(options.eq(0).text()).toBe('blank');
scope.$apply('isBlank = false');
options = element.find('option');
expect(options.length).toBe(1);
expect(options.eq(0).text()).toBe('A');
}
);
From a quick look I can't see an easy way to properly detect (and skip) an emty option with a template directive (ngIf, ngSwitch, ...). Maybe we could hack around it (or special case ngIf as the most popular option (pun intended :stuck_out_tongue:)).
/cc @Narretz
This affects v1.4.7+ and .1.5.0-beta.0+. Here is a CedePen as well.
Nice catch @gkalpak! Looking at it again, the fix looks really fishy. I think we can special-case ngIf by checking if the compiled emptyOption is a comment, and then go from there. I'll play around with it.
Most helpful comment
Seems to break in 1.4.0-beta.6
This works in Firefox 38 & Chrome 43, but not IE 11
<option ng-hide="model" value="">Please choose</option>This works in all three browsers above. Doesn't look great in IE but it works.
<option disabled value="">Please choose</option>