如何响应AngularJS指令中复选框的单击?


79

我有一个AngularJS指令,该指令在以下模板中呈现实体集合:

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

如您所见,在<table>这里可以单独选择每一行并带有自己的复选框,也可以一次选中位于中的主复选框来一次选择所有行<thead>。相当经典的用户界面。

最好的方法是:

  • 选择单行(即,选中复选框时,将所选实体的ID添加到内部数组,然后将CSS类添加到<tr>包含该实体的CSS类以反映其所选状态)?
  • 一次选择所有行?(即,针对中的所有行执行上述操作<table>

我当前的实现是将自定义控制器添加到我的指令中:

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

更具体地说,我想知道:

  • 上面的代码是属于控制器还是应该放在link函数中?
  • 鉴于jQuery不一定存在(AngularJS不需要它),那么进行DOM遍历的最佳方法是什么?没有jQuery,我很难选择<tr>一个给定复选框的父级,或者选择模板中的所有复选框。
  • 传递$eventupdateSelection()似乎并不很优雅。是否有更好的方法来检索刚刚单击的元素的状态(选中/未选中)?

谢谢。

Answers:


122

这就是我一直在做这种事情的方式。Angular倾向于支持对dom的声明式操作,而不是命令性的操作(至少这是我一直在使用的方式)。

标记

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

并在控制器中

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;
};

编辑getSelectedClass()预期整个实体,但仅使用实体的ID进行了调用,现已更正


谢谢,利维!那行得通,而且很有帮助。多亏了您,我已经了解了该ngChecked指令。(我唯一的遗憾是,我们不能使这段代码更加冗长。)
AngularChef,2012年

1
不要将其视为冗长的内容,而应将其视为关注点分离的方面。您的数据模型不应该知道其呈现方式。请记住,在控制器中没有提及tr或td。它最多包含复选框,但是也可以将其排除在外。您可以始终将控制器应用于第二个模板;)
Liviu T.

感谢您提出的问题和答案。我很想知道这种方法的效率含义,因此我制作了以下代码:plnkr.co/edit/T5aZO3s5DzSnbrLELveG 我注意到,每次我选择一个项目,isSelected都会被调用6次(每个转发器项目两次)。知道为什么每个发生两次吗?是否有人担心在页面上投放100多个转发器项目并在移动设备上运行?可能不是问题……
Aaronius

@Aaronius如果在isSelected函数中添加断点并刷新,您将看到在解析和执行指令内容之前调用了该断点。我认为,因为这是一个执行替换的指令,所以所有绑定的函数都被调用了两次
Liviu T.

有没有一种方法只知道选中的复选框?
萨娜·约瑟夫

35

我更喜欢在处理复选框时使用ngModelngChange指令。ngModel允许您将复选框的选中/未选中状态绑定到实体上的属性:

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

每当用户选中或取消选中复选框时,该entity.isChecked值也会更改。

如果这是您所需要的,那么您甚至不需要ngClick或ngChange指令。由于您具有“全部选中”复选框,因此您显然需要做更多的工作,而不仅仅是在有人选中复选框时设置属性的值。

当将ngModel与复选框一起使用时,最好使用ngChange而不是ngClick来处理选中和未选中的事件。ngChange仅适用于这种情况。它利用ngModelController进行数据绑定(它向ngModelController的$viewChangeListeners数组添加了一个侦听器。在设置了模型值之后,将调用此数组中的侦听器,从而避免了此问题)。

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

...以及在控制器中...

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;
};

同样,标头中的“全部检查”复选框:

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

...和...

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

将CSS类添加到<tr>包含实体以反映其选定状态的最佳方法是什么?

如果您使用ngModel方法进行数据绑定,则您需要做的就是将ngClass指令添加到<tr>元素中,以便在实体属性更改时动态添加或删除类:

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

这里查看完整的柱塞


首先将allItemsSelected标志设置为false,然后单击“全选”复选框将其设置为true。你能解释一下吗?
user2514925 '19

11

Liviu的回答对我非常有帮助。希望这不是不好的形式,但我做了一个提琴,可能会在将来帮助别人。

需要的两个重要部分是:

    $scope.entities = [{
    "title": "foo",
    "id": 1
}, {
    "title": "bar",
    "id": 2
}, {
    "title": "baz",
    "id": 3
}];
$scope.selected = [];

1
Angular文档对于检查所有部分有一个更简单的答案。docs.angularjs.org/api/ng.directive:ngChecked。我正在尝试收集被检查的内容。
海顿
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.