AngularJS radio buttons not marked $dirty until last button selected - angularjs

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?

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>

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.

Related

AngularJS possible to click all radio buttons

I create a list of radio button selections, but the problem is that when clicked, they both remain selected, thus not working as a radio button group at all.
I have the same ng-model (a string; 'model') and ng-change for all of them, but the id is different.
<div class="radio-button"
ng-show="vm.forAdmins"
ng-repeat="role in vm.adminRoleDefinitions">
<input id="{{role.name}}" type="radio"
ng-model="role.model"
ng-change="vm.stateChanged(role.name, role.active)" >
{{role.name}}
</div>
Been wrestling with this for a while now, can't see what I've missed.
Radio button will work as a group if you assign name property to those radio buttons. I was also facing this issue then I realized my mistake.
<div class="radio-button" ng-show="vm.forAdmins" ng-repeat="role in vm.adminRoleDefinitions">
<input id="{{role.name}}" type="radio"
ng-model="role.model"
ng-change="vm.stateChanged(role.name, role.active)"
name="roles" >
{{role.name}}
</div>
Try assigning a name attribute to your radio button. name groups the radio button. For example :
<input type="radio" name="someRadio" id="radioOne" />
<input type="radio" name="someRadio" id="radioTwo" />
Now only one is selected at a time.

ng-disabled not working for radio buttons and checkboxes

I am working on an AngularJS application. I am trying to create a page that allows the user to select one of three radio buttons. Two of the three also have checkboxes underneath them to allow the user to select additional options if they've selected the appropriate radio button. To try to prevent improper checkbox selections, I'm trying to set the ng-disabled attribute on the checkboxes. So far, it's not working, and I've tried several different iterations.
This is my HTML:
<div class="panel-body">
<input type="radio" id="notFraudulent" name="actionSelector" ng-model="cleared" /><label for="notFraudulentRadio"> Not Fraudulent</label><br />
<input type="checkbox" id="highVolumeCustomer" ng-model="highVolumeCustomer" ng-disabled="(fraudulent||cleared)" /><label for="highVolumeCustomer"> High Volume Customer</label><br />
<br/>
<input type="radio" id="appearsFraudulent" name="actionSelector" ng-model="fraudulent" /><label for="isFraudulentRadio"> Appears Fraudulent</label><br />
<input type="checkbox" id="reportAccount" ng-model="reportAccount" ng-disabled="(cleared||reviewed)" /><label for="reportAccount"> Report Account</label><br />
<br/>
<input type="radio" id="markReviewed" name="actionSelector" ng-model="reviewed" /><label for="markReviewed"> Mark As Reviewed For Later</label>
</div>
I have tried changing the operator on the ng-disabled expressions to &&, as I've seen some articles where it's suggested that the operators don't mean what one thinks they mean. But that doesn't work, and neither does it work if I put just a single condition in the expression. There isn't anything in the controller (yet) that tries to use or manipulate any of the ng-models in the HTML. I've come to the conclusion that there's something I'm missing with regard to the radio buttons, but I can't for the life of me figure out what.
Can anyone see what my mistake is?
you should use value property to bind special value for radio button, and when radiobutton's status is changed, the value will be kept at ng-model.
refer the code snippet below:
angular.module("app", [])
.controller("myCtrl", function($scope) {
$scope.selectedValue = 'cleared';
$scope.cleared = false;
$scope.fraudulent = false;
$scope.reviewed = false;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="panel-body" ng-app='app' ng-controller="myCtrl">
<input type="radio" id="notFraudulentRadio" name="actionSelector" value="cleared" ng-model="selectedValue" /><label for="notFraudulentRadio"> Not Fraudulent</label><br />
<input type="checkbox" id="highVolumeCustomer" ng-model="highVolumeCustomer" ng-disabled="selectedValue === 'fraudulent' || selectedValue === 'cleared'" /><label for="highVolumeCustomer"> High Volume Customer</label><br />
<br/>
<input type="radio" id="isFraudulentRadio" name="actionSelector" value="fraudulent" ng-model="selectedValue"/><label for="isFraudulentRadio"> Appears Fraudulent</label><br />
<input type="checkbox" id="reportAccount" ng-model="reportAccount" ng-disabled="selectedValue === 'cleared' || selectedValue === 'reviewed'" /><label for="reportAccount"> Report Account</label><br />
<br/>
<input type="radio" id="markReviewed" name="actionSelector" value="reviewed" ng-model="selectedValue"/><label for="markReviewed"> Mark As Reviewed For Later</label>
<br>
cleared:{{cleared}}<br>
fraudulent:{{fraudulent}}<br>
reviewed:{{reviewed}}<br>
selectedValue: {{selectedValue}}
</div>

Radio Buttons ng-checked with ng-model

In my HTML page, I have two sets of Boolean based radio buttons: Labeled: "Yes" and "No" / Values: True and False respectively. I'm populating a full form from a PostgreSQL database table to allow the authenticated user to view the form with populated data and edit the populated fields including the radio buttons, then save the form which will save the data to the DB. All of the other text fields populate without issue; it's both collection of radio buttons I am having an issue with pre-checkmarking the radio buttons.
The below does not pre-populate the checked on front end (but adds the correct attribute of checked in HTML source):
<input id="billing-no" type="radio" name="billing" ng-model="person.billing" value="FALSE" ng-checked="person.billing == 'false'" />
<input id="billing-yes" type="radio" name="billing" ng-model="person.billing" value="TRUE" ng-checked="person.billing == 'true'" />
However, this does check the correct radio button on load:
<input id="billing-no" type="radio" name="billing" value="FALSE" ng-checked="person.billing == 'false'" />
<input id="billing-yes" type="radio" name="billing" value="TRUE" ng-checked="person.billing == 'true'" />
Note: I needed to check against the string boolean value in the directive ng-checked since the boolean value always comes back as a string from PostgreSQL. This, apparently, was a part of PostgreSQL's design when querying data from columns that have boolean data types.
When adding the ng-model directive, the radio button no longer is checked (at least in the rendered browser view). The odd part is that I looked at the source and it clearly checks the correct one. What's even more odd, is that I have to click on the radio button twice to 'check' it. I've tested this in latest version of Chrome, FF, and IE and it all results in the same issue.
The question is: when adding the ng-model directive, why would the HTML source add 'checked' in the radio button attribute, but seemingly does not mark the radio button? Furthermore, why would I have to click twice on the radio button that IS supposed to be checked?
Solution:
To fix this, I removed the ng-checked directive from the radio buttons and only used ng-model as suggested by #Cypher and #aet. I then replaced the attribute value with the directive ng-value "true" & "false". After, I set the values in the controller.
HTML
<input id="billing-no" type="radio" name="billing" ng-model="person.billing" ng-value="false" />
<input id="billing-yes" type="radio" name="billing" ng-model="person.billing" ng-value="true" />
Angular JS
app.controller('peopleCtrl', function($scope, peopleFactory){
...
peopleFactory.getPerson(personParams).then(function(data){
$scope.person = data;
/* moved from ng-checked */
$scope.person.billing = data.billing == 'true';
});
...
};
I think you should only use ng-model and should work well for you, here is the link to the official documentation of angular https://docs.angularjs.org/api/ng/input/input%5Bradio%5D
The code from the example should not be difficult to adapt to your specific situation:
<script>
function Ctrl($scope) {
$scope.color = 'blue';
$scope.specialValue = {
"id": "12345",
"value": "green"
};
}
</script>
<form name="myForm" ng-controller="Ctrl">
<input type="radio" ng-model="color" value="red"> Red <br/>
<input type="radio" ng-model="color" ng-value="specialValue"> Green <br/>
<input type="radio" ng-model="color" value="blue"> Blue <br/>
<tt>color = {{color | json}}</tt><br/>
</form>
I solved my problem simply using ng-init for default selection instead of ng-checked
<div ng-init="person.billing=FALSE"></div>
<input id="billing-no" type="radio" name="billing" ng-model="person.billing" ng-value="FALSE" />
<input id="billing-yes" type="radio" name="billing" ng-model="person.billing" ng-value="TRUE" />
[Personal Option]
Avoiding using $scope, based on John Papa Angular Style Guide
so my idea is take advantage of the current model:
(function(){
'use strict';
var app = angular.module('way', [])
app.controller('Decision', Decision);
Decision.$inject = [];
function Decision(){
var vm = this;
vm.checkItOut = _register;
function _register(newOption){
console.log('should I stay or should I go');
console.log(newOption);
}
}
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div ng-app="way">
<div ng-controller="Decision as vm">
<form name="myCheckboxTest" ng-submit="vm.checkItOut(decision)">
<label class="radio-inline">
<input type="radio" name="option" ng-model="decision.myWay"
ng-value="false" ng-checked="!decision.myWay"> Should I stay?
</label>
<label class="radio-inline">
<input type="radio" name="option" ng-value="true"
ng-model="decision.myWay" > Should I go?
</label>
</form>
</div>
</div>
I hope I could help ;)
Please explain why same ng-model is used? And what value is passed through ng- model and how it is passed? To be more specific, if I use console.log(color) what would be the output?

Set class to checked radio-button in angular?

I'm using ng-repeat to generate a bunch of radiobuttons.
<div class="radiobutton" ng-repeat="mylabel in field.labels">
<input
type="radio"
name="{{field['key']}}"
value="{{mylabel.label}}"
id="{{mylabel.name}}"
>
<label for="{{field['key']}}">
{{mylabel.label}}
</label>
</div>
I would like to add a class to the input-element based on if the input-element is checked or not, using angluar. As far as I can understand I should apply a ng-model to the element and then use that to declare a ng-class, but how do I make it so that each input get's it's own model-name?
try:
<div class="radiobutton" ng-repeat="mylabel in field.labels">
<input
type="radio"
name="{{field['key']}}"
value="{{mylabel.label}}"
id="{{mylabel.name}}"
ng-model='$parent.my_radio_button'
ng-class='{ class_name: (my_radio_button == mylabel.label) }'
>
<label for="{{field['key']}}">
{{mylabel.label}}
</label>
</div>
Since you use radio buttons, I guess only one can be selected, so you can share the same ng model.

Labels, checkboxes and radio buttons

My web application uses forms laid out as in the example below...
First Name [____________]
Last Name [____________]
Gender () Male () Female
The markup I use is something like...
<label for="firstName">First Name</label><input type="text" id="firstName" />
<label for="lastName">Last Name</label><input type="text" id="lastName" />
<label>Gender</label>
<fieldset>
<legend>Gender</legend>
<input type="radio" name="sex" id="sex-m" value="m">
<label for="sex-m">Male</label>
<input type="radio" name="sex" id="sex-f" value="f">
<label for="sex-f">Female</label>
</fieldset>
I have the following issues that I don't know how to solve...
I want to have the WHOLE GROUP of radio buttons labelled like any other field (as in the diagram above), but there is nothing to link the label to (i.e. nothing for its "for" attribute, since each radio in the group has its own label just for the value of the individual radio button) A label without a "for" attribute will not pass accessibility compliance.
The <legend> element of the fieldset seems to duplicate the function of the label. Is this really necessary?
I had thought about styling the <legend> tag to appear as though it's a label, and dispense with the label altogether for the radio button group, but that seems a bit hacky to me, and will also introduce complexities elsewhere in my code (which relies on <label> elements to do some nifty validation message markup and various other things)
Thanks in advance.
The first part of Ssollinger's answer is correct:
The code should be:
<label for="firstName">First Name</label><input type="text" id="firstName" />
<label for="lastName">Last Name</label><input type="text" id="lastName" />
<fieldset>
<legend>Gender</legend>
<input type="radio" name="sex" id="sex-m" value="m">
<label for="sex-m">Male</label>
<input type="radio" name="sex" id="sex-f" value="f">
<label for="sex-f">Female</label>
</fieldset>
When assistive technology hits the male radio button, most will read as: "Gender: male radio button 1 of 2 not selected."
Then you could use CSS on the fieldset, legend, the labels and inputs. If memory serves correctly fieldsets can be a bear to style, so i might end up adding a <div> to it:
<label for="firstName">First Name</label><input type="text" id="firstName" />
<label for="lastName">Last Name</label><input type="text" id="lastName" />
<fieldset>
<legend>Gender</legend>
<div>
<input type="radio" name="sex" id="sex-m" value="m">
<label for="sex-m">Male</label>
<input type="radio" name="sex" id="sex-f" value="f">
<label for="sex-f">Female</label>
</div>
</fieldset>
Adding this <div> has no accessibility implications.
Like in the comment in ssollinger's answer, you could dump the fieldset and legend approach, but you would need to build everything to make it accessible, an example of a build out
I had thought about styling the <legend> tag to appear as though it's a label, and dispense with the label altogether for the radio button group, …
This is the correct way to do it. "Gender" is not a label for anything, the labels for the radio boxes are "male" and "female". "Gender" is the legend of the fieldset which groups the radio buttons together. The correct way to implement this form is to remove the "Gender" label and just leave the fieldset with legend "Gender".
Technically, you could probably add a <div> around the radio buttons and point the for= of the "Gender" label to that, but I'm quite sure that this will cause accessibility problems (haven't tried it with a screen reader though) so I would strongly recommend to get rid of the label for "Gender".

Resources