I use a dynamic name attribute technique. I'm using AngularJS verison 1.5.0.
When I run this AngularJS example (code below), which has nested ng-repeats (so I use $index and $parent.$index), I expect the correct radio button to be checked when rendered.
Check the screenshot below, the second group of radio buttons are checked, the first group aren't checked.
HTML:
<div id="app">
<div ng-controller="RadioController">
<div ng-repeat="outer_level in outer_levels">
<h1>Outer Level {{ $index }}</h1>
<div ng-repeat="inner_level in outer_level.inner_levels">
<h2>Inner Level {{ $index }}</h2>
<input type="radio"
name="radio_{{$parent.$index}}_{{$index}}"
id="radio_{{$parent.$index}}_{{$index}}"
value="All"
ng-model="inner_level.val" />
<label for="radio_{{$parent.$index}}_{{$index}}"> All</label>
<input type="radio"
name="radio_{{$parent.$index}}_{{$index}}"
id="radio_{{$parent.$index}}_{{$index}}"
value="Any"
ng-model="inner_level.val" />
<label for="radio_{{$index}}"> Any</label>
</div>
</div>
</div>
</div>
Javascript:
window.app = angular
.module('app', [])
.controller('RadioController', function ($scope) {
$scope.message = 'Radio';
$scope.outer_levels = [
{
inner_levels: [
{
val: 'All',
},
{
val: 'Any',
},
],
},
];
});
angular.bootstrap(document.getElementById('app'), ['app']);
The radio button checked attribute is set before the dynamic name attribute resolves.
So all 4 radio buttons are using the same name: radio_{{$parent.$index}}_{{$index}}
Checking the second group of radio buttons, unchecks the first group of radio buttons.
Specifically, in the AngularJS source code, I have a breakpoint in the radioInputType controller, in its $render function:
ctrl.$render = function() {
var value = attr.value;
// at this breakpoint, I inspect element[0], and the value of the name attribute is:
// radio_{{$parent.$index}}_{{$index}}
element[0].checked = (value == ctrl.$viewValue);
};
So I have some workarounds:
- Don't use a value for the name attribute -- it's optional, so AngularJS will automatically assign a name attribute. StackBlitz here demonstrates that would work.
- Apparently a radio button can have a value/be "checked" before it has a name attribute; each radio button is considered separate (not part of the same named group; so each radio button can be checked separately).
- But if there is no name attribute, or a unique name attribute assigned to every single radio button, how will the form know which radio buttons belong to the "same group" to take care of the toggling behavior? AngularJS and
ng-modeltakes care of that... (see above StackBlitz); it unchecks other radio buttons that point to the sameng-modelvariable/$viewValue... - Omitting the name attribute is probably the best solution for me; but how can I access the control later to check its
$errorproperty, or$validstate? The$$parentFormdoes not reveal itscontrolsproperty otherwise I could iteratecontrols(without knowing their name) - It seems like the problem comes when interpolating something from the
$parentlike$parent.$index; maybe I could assign a name in advance to the Javascript object, and use that; likeinner_level.assigned_name-- that works
- Add a new
<form>element around eachinner_level, so the radio group names are scoped. StackBlitz here demonstrates that will work.. Suggested in this answer, but might affect your form validation.- Notice that native HTML
<form>elements cannot be nested, so this solution won't work if you have some outer/root<form>element; nested<form>elements are ignored. ng-formdirective- as the AngularJS documentation says, "AngularJS provides the ngForm directive, which behaves identically to form but can be nested"... UPDATE the selector is just
form, and it is not sufficient; using nestedng-formelements does not work for the sake of keeping radio buttons separate
- as the AngularJS documentation says, "AngularJS provides the ngForm directive, which behaves identically to form but can be nested"... UPDATE the selector is just
or the<fieldset>element- UPDATE it looks like
<fieldset>does not create a new "form scope" or "namespace" keeping the radio buttons separate, so if there is some outer form like a root form, won't work...
- UPDATE it looks like
- Notice that native HTML
- If I could modify AngularJS source code (or create my own radio button controller/directive?) I might add code like this, which re-renders; re-assigns the check property if the element name property changes:
scope.$watch(_=>element[0].name, _=>ctrl.$render());- Oddly the
attrobject has the correct name from the very first time the radio button directive is attached, but the DOMelement[0].namename attribute isn't updated until later, and unless I add the above "fix", the radio button is not re-rendered at that point...
- Oddly the
Some things that won't work...
- I could use
ng-initstrategy to create an alias for the outer$index(so I have an alias for$parent.$index), but it doesn't work - In my example I could remove the
{{$parent.$index}}altogether, it works, (which gives insight into how the$renderfunction fires after the name attributes resolve...) but I don't think that fix is sufficient in a scenario with multiple$parentlevels... the names will be ambiguous (i.e.outer_levels[0]andouter_levels[1]would have radio buttons with the same name)
Other ideas:
- Maybe I could update Angular. When I update from 1.5.0 to 1.7.0, it doesn't work...
Any other suggestions for me?
