Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

select with required and only 1 option fails to validate (and set ng-model) until change event is manually triggered #6288

Closed
himdel opened this issue Feb 17, 2014 · 12 comments

Comments

@himdel
Copy link

himdel commented Feb 17, 2014

I have a select element, using ng-option over an array of integers

<form name="form">
    <select ng-model="bar.foo" ng-options="foo as foo|filter for foo in fooarray"  required>
        <option ng-if="fooarray.length > 1" value="" selected>Choose</option>
    </select>
    <input type="submit" ng-disabled="form.$invalid" />
</form>

When the fooarray has only 1 option, it should be chosen by default. Visually, it indeed is.

The DOM looks like this:

<select ng-model="bar.foo" ng-options="foo as foo|filter for foo in fooarray" class="ng-pristine form-control ng-invalid ng-invalid-required" required="">
    <!-- ngIf: fooarray.length > 1 -->
    <option value="0">filtered foo</option>
</select>

But:

  • the select is considered invalid, thus the submit is always disabled
  • the ng-model bar.foo is not set
  • furthemore clicking on the select and re-choosing the same option doesn't trigger an onchange event (presumably because there was indeed no change)

So the form is never actually valid.
Triggering a change event manually ($('select').trigger('change')) indeed sets the model and makes the form valid.

It would seem it's related to the conditional <option>, since when I remove it in the source, the select magically gets another (empty and default) option - but then the only option I wanted is no longer longer default, nor ..the only one. Which is IMHO a second bug.

Version: 1.2.13

@caitp
Copy link
Contributor

caitp commented Feb 17, 2014

Could you post a quick reproduction of this issue to a plnkr?

@himdel
Copy link
Author

himdel commented Feb 17, 2014

Sure, here it is: http://plnkr.co/edit/ZecgGi9L88shcury3wd7?p=preview .

@pkozlowski-opensource
Copy link
Member

@himdel what about using data binding to select the single available option?
http://plnkr.co/edit/dB5AS7IP8KbIMSC0Fd2D?p=preview

A bit less code to write and seems to work as you would expect, no?

If you don't care about the empty option name, the HTML can be simplified a bit:
http://plnkr.co/edit/GEqmN4fMauTycNzjtuHP?p=preview

Does it work for you?

@himdel
Copy link
Author

himdel commented Feb 17, 2014

@pkozlowski-opensource thanks...

I do care about the empty option so the second version doesn't work for me.

As for the first one... it's definitely much nicer than the hack I came up with (adding a directive that triggers change after a timeout), so thanks for that :).

However, I use the form for both create and edit, so adding something akin to if (mode == "new" && $scope.fooarray.length == 1) { $scope.bar.foo = $scope.fooarray[0]; } works, it still feels ..wrong, somehow.

Additionally, it has the problem that if the existing bar object (in edit mode) has the foo attribute set to something not in the array, (eg. bar.foo == 2), there's still no way for the user the select the 1.

But really, it's not that I can't work around that issue, that's fine ... what bugs me is that IMO ng-model on a select should always match the select's state, which doesn't happen here, so I think that part is a bug on angular's part. When creating the issue, I wasn't in the problem-solving state, I'd probably use stackoverflow for that, I was in bug-reporting state, if that makes sense :).

But thanks anyway.

@pkozlowski-opensource
Copy link
Member

@himdel yes, I agree that this is a corner case, but I wasn't into the hack-proposing mode :-) The thing is that IMO selection should be driven from the model. If there is no proper model selection AngularJS would try to generate an empty option anyway (http://stackoverflow.com/questions/12654631/why-does-angularjs-include-an-empty-option-in-select/12654812#12654812), we are just killing it here with ngIf, hence the tricky scenario. But IMO it doesn't change the fact that it should be model driving the selection.

@himdel
Copy link
Author

himdel commented Feb 17, 2014

@pkozlowski-opensource I'm not sure about the model driving the selection, or rather, I think the user should be doing that, and AFAICT the user has no way of influencing the model directly. But yeah, let's not get philosophical :).

I do agree that the ngIf is the core problem here, and without it, there would be no problem, so I'll try to rethink that part. Probably I'll keep the "choose" option always there (remove the ngIf) and just preselect the first real option if none is set and the list has length 1.

Thanks for helping me through that.

Anyway, should I close the issue? I mean, on the one hand, I don't really have a problem anymore, but on the other, there is a (albeit rather specific) case where angular model doesn't match the state of the corresponding select..

@sebdavid
Copy link

Hello,

Using v1.2.13, I was facing to a similar problem, when I read this thread.
Here is my example : http://plnkr.co/edit/DNLH2te8oxhVW3isN5E0?p=preview
And the steps to reproduce the "problem" :

  1. click the edit button
  2. submit the form : the general message "There are form errors" is displayed, but not the message near to the select, i.e. "Select a color !".

As @himdel wrote, if the select is used (performing the "change" event), then it works as expected.

@pkozlowski-opensource I understand the explanation given in your SO link, that Angular "don't want to decide model value on its own". But in my example, one difference with @himdel example is that the options are not generated with ng-options. I cannot find a way to link the first option to the model.

Do you think there is a solution to manage this case ?

@himdel
Copy link
Author

himdel commented Feb 20, 2014

@sebdavid Hi, your case can be solved trivially, by removing the empty option and the required, angular will automagically add an empty option for you, and you don't really want required if you're OK with the user chosing the empty option. Unless I'm misunderstanding something :)

@sebdavid
Copy link

@himdel that's the point : I want the field to be required, and the select error message to be displayed.

But... after lunch it seems that the solution is so obvious : in my plnkr example, just remove the condition !userForm.color.$pristine in the select error span, and it works...

Finally, my problem was not really the same as yours, because I'm validating fields after the user submits the form. That's why I'm using $pristine on form elements, but for this case, it is useless on the select element.

@vcardins
Copy link

vcardins commented May 7, 2015

Quite old thread but the solution posted here: #2365 (romario333) worked for me, in short:

module.directive('select', HackedSelect);
    /* @ngInject */
    function HackedSelect() {
        return {
            restrict: 'E',
            require: '?ngModel',
            link: function(scope, element, attr, ngModelCtrl) {
                if (ngModelCtrl) {
                    ngModelCtrl.$isEmpty = function(value) {
                        return !value || value.length === 0;
                    }
                }
            }
        }
    }

@Narretz
Copy link
Contributor

Narretz commented Apr 11, 2016

The second version in original plunker works now (ngIf in the empty option is supported since 1.4.9 at least). The third version works as intended. If you want the first and only option to be auto-selected, you have to set it in the model. That's how angular works.

@Narretz Narretz closed this as completed Apr 11, 2016
@vaske
Copy link

vaske commented Jan 13, 2018

Is there any way to remove red line in first example where it's required select and have default value which have value="" ?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

10 participants