AngularJS radio buttons not marked $dirty until last button selected
Asked Answered
M

2

9

I created this simple example: http://jsfiddle.net/5Bh59/.

If you switch between AngularJS 1.2.1 and 1.1.1, you'll see the radio buttons don't work properly in either version. If you watch the radio button's $dirty field, 1) for version 1.1.1, it will only be set when the first button is clicked, and 2) for version 1.2.1, it will only be set when the last button is clicked.

I read this answer: AngularJS Radio group not setting $dirty on field but I don't really understand the answer. Not only that but the fiddler example demonstrates the same behavior.

So, is this a bug in AngularJS and how can I work around it?

Mutualize answered 4/3, 2014 at 19:45 Comment(4)
That will work. I was surprised that $valid becomes true for both controls even though only one is selected.Mutualize
I'm guessing that's because they use the same ng-model, so all the inputs can tell if there is one selected in the group.Wounded
The problem is each also has its own $dirty now, too. I only like to show validation if a control is marked $dirty.Mutualize
You can wrap an ng-form around all the radio buttons and call it something like "radioGroup", then it will be marked $dirty as soon as any of the radio buttons are clicked. I'll add another fiddle.Wounded
W
12

You either need to give each radio button input a different name, or you need to wrap each radio button in an ng-form (each of which have a different name). If you use two inputs with the same name in the same form, only the last one will be bound to the property on the FormController. If you use different names, then each input will have its own property on the FormController.

Example with different names for each radio button: http://jsfiddle.net/BEU3V/

<form name="form" novalidate>
    <input type="radio" 
        name="myRadio1" 
        ng-model="myRadio" 
        ng-click="" 
        value="Rejected"
        required>Rejected<br />
    <input type="radio" 
        name="myRadio2" 
        ng-model="myRadio"
        ng-click=""
        value="Approved"
        required>Approved<br />
   Form $dirty: {{form.$dirty}}<br />
   Field1 $dirty: {{form.myRadio1.$dirty}}<br />
   Field1 $dirty: {{form.myRadio2.$dirty}}<br />
   Value: {{myRadio}}
</form>

Example wrapping with ng-form: http://jsfiddle.net/39Rrm/1/

<form name="form" novalidate>
    <ng-form name="form1">
    <input type="radio" 
        name="myRadio" 
        ng-model="myRadio" 
        ng-click="" 
        value="Rejected"
        required>Rejected<br />
        </ng-form>
    <ng-form name="form2">    
    <input type="radio" 
        name="myRadio" 
        ng-model="myRadio"
        ng-click=""
        value="Approved"
        required>Approved<br />
        </ng-form>
   Form $dirty: {{form.$dirty}}<br />
   Field1 $dirty: {{form.form1.myRadio.$dirty}}<br />
   Field2 $dirty: {{form.form2.myRadio.$dirty}}<br />
   Value: {{myRadio}}
   </form>

If you'd like a single check for the radio group, you can wrap all the radio buttons in their own ng-form and call it something like name="radioGroup".

http://jsfiddle.net/6VVBL/

<form name="form" novalidate>
    <ng-form name="radioGroup">
    <input type="radio" 
        name="myRadio1" 
        ng-model="myRadio" 
        ng-click="" 
        value="Rejected"
        required>Rejected<br />
    <input type="radio" 
        name="myRadio2" 
        ng-model="myRadio"
        ng-click=""
        value="Approved"
        required>Approved<br />
        </ng-form>
   Form $dirty: {{form.$dirty}}<br />
   Group $valid: {{form.radioGroup.$valid}}<br />
   Group $dirty: {{form.radioGroup.$dirty}}<br />
   Value: {{myRadio}}
   </form>
Wounded answered 4/3, 2014 at 20:2 Comment(4)
I ended up going with option #3. I was using a validation directive that would surround a control with a <div class="has-error"> and add a <span> describing the error. Sadly, it didn't work because there are two controls with the same name, and it only works on ngModel, not form. I just duplicated the logic, made it work with the outer form and now everything is working fine.Mutualize
It is a good thing to summarize all the different approaches in one place, too.Mutualize
feels broken that "If you use two inputs with the same name in the same form, only the last one will be bound to the property on the Form". Html requires radios from the same group to have the same name. However it will do this weird thing in Angular... Perhaps angular needs to revisit how it treats radios/checkboxesCarat
Also this doesn't work if one wants to use the $touched property. Might make sense to actually introduce a different/custom directive that is similar to the Material Design's md-radio-group directiveKinny
G
1

This answer is related but perhaps not exactly applicable, but after finding and reading this item I felt it valuable to provide, and I don't have enough points to just comment on an answer (which I thought would have been a more appropriate way to respond).

My issue was that I wanted to show a required error (using ng-messages) but when you tabbed through / past the radio button group $touched didn't turn true unless you shift-tabbed back from the next UI element back to the last radio button of the group. (When my form renders the radio buttons are not set - I'm wanting the user to make a selection and not rely on the user accepting a default.)

Here's my code:

<div class="form-group" ng-class="{'has-error': pet.genderId.$invalid && pet.genderId.$touched}">
    <label class="control-label">
        What is your pet's gender?
        <span ng-messages="pet.genderId.$error" ng-show="pet.genderId.$invalid && pet.genderId.$touched">
            <span ng-message="required">(required)</span>
        </span>
    </label>
    <div>
        <label class="radio-inline"><input type="radio" ng-model="genderId" name="genderId" value="1" required ng-blur="pet.genderId.$setTouched();" />Male</label>
        <label class="radio-inline"><input type="radio" ng-model="genderId" name="genderId" value="2" required ng-blur="pet.genderId.$setTouched();" />Female</label>
        <label class="radio-inline"><input type="radio" ng-model="genderId" name="genderId" value="3" required ng-blur="pet.genderId.$setTouched();" />Not sure</label>
    </div>
</div>

The 'magic' was adding the ng-blur attribute to set 'touched' myself even if only the first radio button was tabbed past.

You may be able to employ a similar tactic for $dirty by calling $setDirty() in the ng-changed attribute.

Grigri answered 11/1, 2016 at 18:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.