1

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

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-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?

1 Answer 1

1

Hey the problem was caused due to the interpolation on the id and name field, I changed it to ng-id and ng-name so that we do not use interpolation, then it started working.

Before:

    <input
      type="radio"
      name="radio_{{$parent.$index}}_{{$index}}"
      id="radio_{{$parent.$index}}_{{$index}}"
      value="All"
      ng-model="inner_level.val"
    />

After:

    <input
      type="radio"
      ng-name="'radio_' + $parent.$index + $index"
      ng-id="'radio_' + $parent.$index + $index"
      value="All"
      ng-model="inner_level.val"
    />

window.app = angular
  .module('app', [])
  .controller('RadioController', function($scope) {
    $scope.message = 'Radio';
    $scope.val = 'All'
    $scope.outer_levels = [{
      inner_levels: [{
          val: 'All',
        },

        {
          val: 'Any',
        },

        {
          val: 'Any',
        },
      ],
    }, ];
  });
angular.bootstrap(document.getElementById('app'), ['app']);
<script src="https://code.angularjs.org/1.5.0/angular.min.js"></script>

<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"
          ng-name="'radio_' + $parent.$index + $index"
          ng-id="'radio_' + $parent.$index + $index"
          value="All"
          ng-model="inner_level.val"
        />
        <label for="radio_{{$parent.$index}}_{{$index}}"> All</label>
        <input
          type="radio"
          ng-name="'radio_' + $parent.$index + $index"
          ng-id="'radio_' + $parent.$index + $index"
          value="Any"
          ng-model="inner_level.val"
        />
        <label for="radio_{{$index}}"> Any</label>
      </div>
    </div>
  </div>
</div>

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.