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: http://plnkr.co/IQA9kq
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.
Thanks for documenting this! I was struggling with an ng-model on the “Select All” box too.
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?
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.
Thanks, then i understand why it works 🙂
Hi, the plunkr seems to be empty?
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.
Hmmm, just tried again – Plunker seems to hate me. Any chance of a jsfiddle?
OK. If you aren’t able to access the original Plunker example, here is a Codepen.io equivalent that will hopefully work for you: http://codepen.io/JohnMunsch/pen/yaouk
Thanks John, I hope it’ll help other people having trouble with Plunker in the future as well 🙂
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!
Well Done, I found this helpful
What if you have another level of checkboxes, like after “Get Safety” there will be another set of choices, will this still work?
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.
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
{{ animal.name }}
{{ reptile.name }}
{{ bird.name }}
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 🙂
here’s the link –> http://codepen.io/anon/pen/gpeBdQ
what is _.reduce in angularjs? thanks so much for reply!
An excellent question. In my example I used both _.each() and _.reduce() functions. These are available from either the Underscore.js (http://underscorejs.org/) or Lodash (https://lodash.com/) libraries. Both of which offer a huge set of functions to perform various forms of processing on objects or arrays in your JavaScript code.
JavaScript itself has started to add similar functions so you don’t have to use libraries to get these particular functions (forEach – https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach and reduce – https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce). So you could use those instead if you don’t want to add an extra library only to get a handful of functions and if you don’t have to support older browsers without the functions. Check the bottom of each page for browser versions with support for the given function.