Angular.js: ngOptions in Angular 1.4 broke <select> placeholder values using ng-if

Created on 23 Jun 2015  路  9Comments  路  Source: angular/angular.js

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

forms inconvenient regression bug

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>

All 9 comments

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.

Was this page helpful?
0 / 5 - 0 ratings