AngularJS (1.5.0) nested ng-repeat with radio button not initializing checked properly

30 Views Asked by At

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.

screenshot of the initial rendering of this stackblitz https://stackblitz.com/edit/angularjs-rzsjbr?file=index.html -- the second group of radio buttons are checked properly but the first group of radio buttons are not checked

    <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-model takes care of that... (see above StackBlitz); it unchecks other radio buttons that point to the same ng-model variable/$viewValue...
    • Omitting the name attribute is probably the best solution for me; but how can I access the control later to check its $error property, or $valid state? The $$parentForm does not reveal its controls property otherwise I could iterate controls (without knowing their name)
    • It seems like the problem comes when interpolating something from the $parent like $parent.$index; maybe I could assign a name in advance to the Javascript object, and use that; like inner_level.assigned_name -- that works
  • Add a new <form> element around each inner_level , so the radio group names are scoped. StackBlitz here demonstrates that will work.. Suggested in this answer, but might affect your form validation.
  • 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 attr object has the correct name from the very first time the radio button directive is attached, but the DOM element[0].name name attribute isn't updated until later, and unless I add the above "fix", the radio button is not re-rendered at that point...

Some things that won't work...

Other ideas:

Any other suggestions for me?

0

There are 0 best solutions below