Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to respond to clicks on a checkbox in an AngularJS directive?

I have an AngularJS directive that renders a collection of entities in the following template:

<table class="table">   <thead>     <tr>       <th><input type="checkbox" ng-click="selectAll()"></th>       <th>Title</th>     </tr>   </thead>   <tbody>     <tr ng-repeat="e in entities">       <td><input type="checkbox" name="selected" ng-click="updateSelection($event, e.id)"></td>       <td>{{e.title}}</td>     </tr>   </tbody> </table> 

As you can see, it's a <table> where each row can be selected individually with its own checkbox, or all rows can be selected at once with a master checkbox located in the <thead>. Pretty classic UI.

What is the best way to:

  • Select a single row (i.e. when the checkbox is checked, add the id of the selected entity to an internal array, and add a CSS class to the <tr> containing the entity to reflect its selected state)?
  • Select all rows at once? (i.e. do the previously described actions for all rows in the <table>)

My current implementation is to add a custom controller to my directive:

controller: function($scope) {      // Array of currently selected IDs.     var selected = $scope.selected = [];      // Update the selection when a checkbox is clicked.     $scope.updateSelection = function($event, id) {          var checkbox = $event.target;         var action = (checkbox.checked ? 'add' : 'remove');         if (action == 'add' & selected.indexOf(id) == -1) selected.push(id);         if (action == 'remove' && selected.indexOf(id) != -1) selected.splice(selected.indexOf(id), 1);          // Highlight selected row. HOW??         // $(checkbox).parents('tr').addClass('selected_row', checkbox.checked);     };      // Check (or uncheck) all checkboxes.     $scope.selectAll = function() {         // Iterate on all checkboxes and call updateSelection() on them??     }; } 

More specifically, I wonder:

  • Does the code above belong in a controller or should it go in a link function?
  • Given that jQuery is not necessarily present (AngularJS doesn't require it), what's the best way to do DOM traversal? Without jQuery, I'm having a hard time just selecting the parent <tr> of a given checkbox, or selecting all checkboxes in the template.
  • Passing $event to updateSelection() doesn't seem very elegant. Isn't there a better way to retrieve the state (checked/unchecked) of an element that was just clicked?

Thank you.

like image 268
AngularChef Avatar asked Aug 08 '12 20:08

AngularChef


People also ask

How check if checkbox is clicked?

# jQuery Code: Use .on('click',functino(){ $("#myCheckBox"). prop("checked", true); }); Here in above code on button click, we have use . prop() method and set attribute checked as true, which marked as checked.

How do you checked the checkbox in angular?

AngularJS ng-checked Directive The ng-checked directive sets the checked attribute of a checkbox or a radiobutton. The checkbox, or radiobutton, will be checked if the expression inside the ng-checked attribute returns true.

Which directive in AngularJS will be executed when the element is being clicked?

The ng-click directive defines AngularJS code that will be executed when the element is being clicked.


2 Answers

This is the way I've been doing this sort of stuff. Angular tends to favor declarative manipulation of the dom rather than a imperative one(at least that's the way I've been playing with it).

The markup

<table class="table">   <thead>     <tr>       <th>         <input type="checkbox"            ng-click="selectAll($event)"           ng-checked="isSelectedAll()">       </th>       <th>Title</th>     </tr>   </thead>   <tbody>     <tr ng-repeat="e in entities" ng-class="getSelectedClass(e)">       <td>         <input type="checkbox" name="selected"           ng-checked="isSelected(e.id)"           ng-click="updateSelection($event, e.id)">       </td>       <td>{{e.title}}</td>     </tr>   </tbody> </table> 

And in the controller

var updateSelected = function(action, id) {   if (action === 'add' && $scope.selected.indexOf(id) === -1) {     $scope.selected.push(id);   }   if (action === 'remove' && $scope.selected.indexOf(id) !== -1) {     $scope.selected.splice($scope.selected.indexOf(id), 1);   } };  $scope.updateSelection = function($event, id) {   var checkbox = $event.target;   var action = (checkbox.checked ? 'add' : 'remove');   updateSelected(action, id); };  $scope.selectAll = function($event) {   var checkbox = $event.target;   var action = (checkbox.checked ? 'add' : 'remove');   for ( var i = 0; i < $scope.entities.length; i++) {     var entity = $scope.entities[i];     updateSelected(action, entity.id);   } };  $scope.getSelectedClass = function(entity) {   return $scope.isSelected(entity.id) ? 'selected' : ''; };  $scope.isSelected = function(id) {   return $scope.selected.indexOf(id) >= 0; };  //something extra I couldn't resist adding :) $scope.isSelectedAll = function() {   return $scope.selected.length === $scope.entities.length; }; 

EDIT: getSelectedClass() expects the entire entity but it was being called with the id of the entity only, which is now corrected

like image 135
Liviu T. Avatar answered Oct 21 '22 09:10

Liviu T.


I prefer to use the ngModel and ngChange directives when dealing with checkboxes. ngModel allows you to bind the checked/unchecked state of the checkbox to a property on the entity:

<input type="checkbox" ng-model="entity.isChecked"> 

Whenever the user checks or unchecks the checkbox the entity.isChecked value will change too.

If this is all you need then you don't even need the ngClick or ngChange directives. Since you have the "Check All" checkbox, you obviously need to do more than just set the value of the property when someone checks a checkbox.

When using ngModel with a checkbox, it's best to use ngChange rather than ngClick for handling checked and unchecked events. ngChange is made for just this kind of scenario. It makes use of the ngModelController for data-binding (it adds a listener to the ngModelController's $viewChangeListeners array. The listeners in this array get called after the model value has been set, avoiding this problem).

<input type="checkbox" ng-model="entity.isChecked" ng-change="selectEntity()"> 

... and in the controller ...

var model = {}; $scope.model = model;  // This property is bound to the checkbox in the table header model.allItemsSelected = false;  // Fired when an entity in the table is checked $scope.selectEntity = function () {     // If any entity is not checked, then uncheck the "allItemsSelected" checkbox     for (var i = 0; i < model.entities.length; i++) {         if (!model.entities[i].isChecked) {             model.allItemsSelected = false;             return;         }     }      // ... otherwise ensure that the "allItemsSelected" checkbox is checked     model.allItemsSelected = true; }; 

Similarly, the "Check All" checkbox in the header:

<th>     <input type="checkbox" ng-model="model.allItemsSelected" ng-change="selectAll()"> </th> 

... and ...

// Fired when the checkbox in the table header is checked $scope.selectAll = function () {     // Loop through all the entities and set their isChecked property     for (var i = 0; i < model.entities.length; i++) {         model.entities[i].isChecked = model.allItemsSelected;     } }; 

CSS

What is the best way to... add a CSS class to the <tr> containing the entity to reflect its selected state?

If you use the ngModel approach for the data-binding, all you need to do is add the ngClass directive to the <tr> element to dynamically add or remove the class whenever the entity property changes:

<tr ng-repeat="entity in model.entities" ng-class="{selected: entity.isChecked}"> 

See the full Plunker here.

like image 42
Kevin Aenmey Avatar answered Oct 21 '22 09:10

Kevin Aenmey