15

I am trying to force a single-selection on checkboxes, similar to a html "select"

I have a html simple table:

<tr ng-repeat="subscription in entities">
    <td>
        <input type="checkbox" ng-checked="isChecked(subscription)" ng-click="toggleSelection(subscription)"/>
    </td>
</tr> 

Then I have some simple controller functions for those directives above:

$scope.isChecked = function(entity) {
    return $scope.checkedEntity === entity;
};

$scope.toggleSelection = function(entity) {
    entity.checked = !entity.checked;
    if (entity.checked) {
        $scope.checkedEntity = entity;
    } else {
        $scope.checkedEntity = null;
    }
};

Unfortunately it doesn't work, and I think I just discovered why.... the ng-click has 0 priority, vs 100 for ng-checked.

Is there an elegant solution for this problem?

3
  • can you share your $scope objects Commented Apr 24, 2014 at 21:42
  • I'm sorry I don't get what you mean? Commented Apr 24, 2014 at 21:57
  • why not just use type="radio"? Commented Jul 17, 2018 at 13:24

4 Answers 4

25

Bind ng-model to subscription.checked, and have ng-click uncheck all subscriptions except the one clicked. Since these are checkboxes, the one clicked will toggle itself.

<tr ng-repeat="subscription in entities">
  <td>
    <input ng-model="subscription.checked" ng-click="updateSelection($index, entities)" type="checkbox" />
  </td>
</tr>

You can use a plain for loop, but angular's forEach allows us to alias each item as subscription and improve readability:

$scope.updateSelection = function(position, entities) {
  angular.forEach(entities, function(subscription, index) {
    if (position != index) 
      subscription.checked = false;
  });
}

Here is a working demo: http://plnkr.co/edit/XD7r6PoTWpI4cBjdIZtv?p=preview

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

3 Comments

Awesome, thanks a lot! Interesting, I originally tried a tiny variation of that where I only pass the current entity on ng-click, and the function iterates over the $scope.entities which should be the same data structure... but for some reason yours works and mine doesn't!
This syntax is nicer :P entities.map((x, i) => x.checked = position === i);
Hey i have simple layout but instead of checkboxes, i have divs. How to add the active class with same logic.? Only one div should get active class and also on click of active div, it should toggle its class
8

<!DOCTYPE html>
<html>

<head>
  <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
  <script>
    angular.module('app', []).controller('appc', ['$scope',
      function($scope) {
        $scope.selected = 'other';
      }
    ]);
  </script>
</head>

<body ng-app="app" ng-controller="appc">
  <label>SELECTED: {{selected}}</label>
  <div>
    <input type="checkbox" ng-checked="selected=='male'" ng-true-value="'male'" ng-model="selected">Male
    <br>
    <input type="checkbox" ng-checked="selected=='female'" ng-true-value="'female'" ng-model="selected">Female
    <br>
    <input type="checkbox" ng-checked="selected=='other'" ng-true-value="'other'" ng-model="selected">Other
  </div>



</body>

</html>

Comments

1

I have used the below code to achieve the similar functionality. Trick is to use ng-click which can yield this pretty nicely. checkbox-1 & checkbox-2 are boolean in nature.

<form>
    <input type="checkbox" ng-click="checkbox-2 = false" ng-model="checkbox-1" >
    <input type="checkbox" ng-click="checkbox-1 = false" ng-model="checkbox-2" >
</form>

Comments

0

Off the top of my head, I'd suggest one of three options for you.

  1. Write a directive that will set up the checkboxes for you and manage the state within them. This isn't ideal, because you're turning a rule about your model (only one subscription should be checked) into DOM manipulation problem.
  2. Structure your model such that only one subscription is ever marked active. This is tricky, because modifying the model on a change will kick off another digest cycle, not what you want.
  3. Use radio buttons instead of checkboxes. That will get you the modality you want. :-P

I'd go with option 3--I'm always a fan of taking advantage of native input elements.

<tr ng-repeat="subscription in entities">
<td><input type="radio"
   ng-model="selection"
   name="subscriptionRadio"
   value="{{subscription}}"/>
</td> 
</tr>

3 Comments

For 1) agreed, unpleasant. For 3) Right! I had a big argument with the UX designer about UI standards dating back not simply years but over a decade, but sadly it MUST be checkboxes... fist shaking For #2, I tried originaly with a ng-model tied to the subscription, and then a ng-changed that would then update the model across all the subscriptions. But in this case, the model was updated, but the "checked" state didn't change, unless I used jquery, but I'm trying to avoid that.
What you can do is set up the directive such that it triggers on the model change and reconfigures the checkboxes. "two-way" binding is definitely a thing in Angular directives, and it might help you out here.
Also, I figured you would have gone with radio buttons if you could have. ;)

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.