ng-checked and ng-click gotcha

Just wanted to take a moment to document a gotcha with angular's ng-checked and ng-click directives.

Suppose you have the following angular code. Whenever a user clicks on the checkbox or on the label, you want to trigger the toggleColor() function.

  <div class="color" ng-repeat="color in colors">
      <label ng-click="toggleColor(color)">
          <input type="checkbox" ng-checked="isSelected(color)"/>
          {% raw %}Click to select {{color}}{% endraw %}
      </label>
  </div>

The problem with this bit of code is that if you click the label it will call toggleColor() twice! If you click the checkbox, the function is only called once, as expected.

Try it out and see the full code in this js fiddle: http://jsfiddle.net/bLf5f/

So what's happening? It turns out this is not a problem with angular, but with the way browsers handle clicks to labels that contain checkboxes. Whenever you click a DOM element inside a label that contains a checkbox, the click event is triggered twice: once on the label and once on the checkbox. The click event on the checkbox bubbles up to the label and so your ng-click is called twice.

One way to fix this is to move the ng-click listener to the checkbox. This is ok even if you want the listener to be applied to other dom elements inside the label.

  <div class="color" ng-repeat="color in colors">
      <label>
          <input type="checkbox" ng-checked="isSelected(color)" ng-click="toggleColor(color)"/>
          {% raw %}Click to select {{color}}{% endraw %}
      </label>
  </div>

Props to @wmill for figuring this one out.