An AngularJS pattern for “all” checkboxes

Recently I had to do an interface at work where there were a list of items, each with a separate checkbox, and a separate “all” checkbox which would check or uncheck all of them. At first that doesn’t seem very complicated but it’s an easy interface to mess up because you want the “all” to become checked if the user manually selects every child checkbox. Or conversely, if “all” is checked then you want it to automatically uncheck if even one child is unchecked.

My page had three separate sections with different lists and an all checkbox for each one. I had done my original design with an extra $scope variable representing the “all” checkbox and used a watch, but it still managed to mess up in certain circumstances. It was overly complicated and didn’t work 100% of the time. So my colleague challenged me to build it without the extra $scope variable because he was certain that was the key to getting a flawless solution. Now that I’ve done it, I agree completely.

Here’s a Plnkr with a running copy of the code so you can play with it:

But I just wanted to point out that what makes this work is really this line here. Instead of having the “all” checkbox reflect some variable via ng-model binding, I’ve just hooked it to two functions which handle the action it should perform (no more watch) and another which governs when it does and doesn’t appear checked.

  <input type="checkbox" ng-click="allNeedsClicked()" 
      ng-checked="allNeedsMet()" />All needs

In the next day or two I’m going to add this one to my AngularJS example page but I figured if I wrote about it now I would be more likely to get it done.

15 thoughts on “An AngularJS pattern for “all” checkboxes

  1. Christoffer

    This works great, I’m just having a hard time wrapping my head around how this actually works. I understand the part of the parent checkbox when you click it all other checkboxes become checked and vise versa. The part i don’t get is how can clicking any of the checkboxes in the list impact the parent checkbox and run it’s functions? Is it be because of angulars digest cycle constantly running the function in the ng-checked directive?

    1. John Munsch Post author

      AngularJS runs its digest cycle in response to certain events, constantly might imply something that is timer based or running continuously. In this case, the change in the status of the other checkboxes does run a digest cycle so the parent’s checkbox gets updated whenever one of them changes.

    1. John Munsch Post author

      Perhaps try again, sometimes Plunker gets overwhelmed and doesn’t work right. But I just double checked it and it seemed to be working and I made sure I wasn’t logged in so I wasn’t seeing something that wasn’t visible to everybody.

  2. Pierre

    Very useful, thank you John! And bonus points for using underscore. Another option would have been to use _.every with todo.done instead of a reduce and a compare with the number of items.

    I appreciate your post!

  3. savidlab

    What if you have another level of checkboxes, like after “Get Safety” there will be another set of choices, will this still work?

    1. John Munsch Post author

      Since the All checkbox is now driven off of two different functions (one to know whether it should appear checked or not and another to take an action when it is checked or unchecked), it would be up to you to decide what happens with those other checkboxes. Are they checked and unchecked when you check the master checkbox? Does the master checkbox depend upon whether those are checked or not?

      Since you can imagine logic in those two functions which takes other checkboxes into consideration I think the answer would be yes. The tricky thing would be if the “All” checkbox was looking at a set of other checkboxes and one of those was itself an “All” checkbox for a set of child checkboxes. That could get tricky to code up, but even then it should be possible to do it.

  4. savidlab

    So i made some updates to what you did, i just think it would also be awesome if readers will get something like this.

    html below:

    All Animals

    {{ }}

    {{ }}

    {{ }}

    script below:
    function TodoCtrl($scope) {
    $scope.animals = [
    {name: ‘All Fishes’, isSelected: false},
    {name: ‘All Birds’, isSelected: false},
    {name: ‘All Reptiles’, isSelected: false},
    {name: ‘All Mammals’, isSelected: false},
    {name: ‘All Insects’, isSelected: false}

    $scope.reptiles = [
    {name: ‘Snakes’, isSelected: false},
    {name: ‘Turtles’, isSelected: false},
    {name: ‘Crocodiles’, isSelected: false},
    {name: ‘Frogs’, isSelected: false}

    $scope.birds = [
    {name: ‘Robin’, isSelected: false},
    {name: ‘Hawk’, isSelected: false},
    {name: ‘Eagle’, isSelected: false},
    {name: ‘Cardinal’, isSelected: false}

    $scope.allAnimalsClicked = function () {
    var newValue = !$scope.allAnimalsSelected();

    _.each($scope.animals, function (animal) {
    animal.isSelected = newValue;

    // Returns true if and only if all animals are selected.
    $scope.allAnimalsSelected = function () {
    var needsMet = _.reduce($scope.animals, function (memo, animal) {
    return memo + (animal.isSelected ? 1 : 0);
    }, 0);

    return (needsMet === $scope.animals.length);

    The only missing part is the selection of all animals and the uncheck/check all, i’m still modifying your original and i’m just fascinated by it. I hope you don’t mind :)


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s