AngularJS: Reverse Checkbox State

Somehow, through the magic of Angular, if you use ng-model and provide it a boolean, your checkbox will be checked if said boolean is true, and unchecked if false.

<input type="checkbox" ng-model="video.hidden">

While this alone is fairly baffling, I'm actually trying to reverse the checked state, because unlike the todo example where todo.done means the box gets checked, my model is more like todo.incomplete.

Unfortunately my first guess didn't work:

<input type="checkbox" ng-model="!video.hidden">

I'm in a position where the model has been dictated to me, so I can't change it and don't want to have to massage it on the client (because I'm sending client objects back to the server, since it's running in a trusted environment).

Update

This works in 1.3 and doesn't give you strings (1.2.xxx gave you strings instead of booleans):

<input type="checkbox" ng-model="video.hidden" ng-true-value="false" ng-false-value="true">

Solution 1:

You can make that now without a need of custom directive, angular support ng-true-value and ng-false-value

https://docs.angularjs.org/api/ng/input/input[checkbox]

your example should work now <input type="checkbox" ng-model="video.hidden" ng-true-value="false" ng-false-value="true">

Solution 2:

I don't think there's a way to do this purely in the template. The most obvious way to accomplish this is create a new scope variable that's manually double-bound to your model value. So like:

<input type="checkbox" ng-model="videoShowing">

And in your controller:

$scope.videoShowing = !$scope.video.hidden;

$scope.$watch('video.hidden', function(value) {
  $scope.videoShowing = !value;
});
$scope.$watch('videoShowing', function(value) {
  $scope.video.hidden = !value;
}

You could almost just use one-way binding with an ng-change, but that doesn't quite work:

<input type="checkbox" ng-checked="!video.hidden" ng-change="???">

Because ng-change doesn't mix the current value into the local scope at all. If you were in a context where you had access to the NgModelController you could do something, but you'd need a custom directive for that. Wouldn't be hard to make an "inverted" attribute directive that modifies the NgModel's formatters and parsers. I'd suggest that if you need lots of inverted checkboxes.

Edit

For posterity, making an "inverted" attribute is super simple, might as well just do that. Making directives always seems like a pain at first, but it really is the Angular way.

someModule.directive('inverted', function() {
  return {
    require: 'ngModel',
    link: function(scope, element, attrs, ngModel) {
      ngModel.$parsers.push(function(val) { return !val; });
      ngModel.$formatters.push(function(val) { return !val; });
    }
  };
});

Might need to set the priority too, or "unshift" instead of push, to make sure these guys run after the built-in inputDirective ones.

Solution 3:

This seems to work

<input  type="checkbox" 
        ng-init = "video.checked = !video.hidden"
        ng-model ="video.checked"
        ng-change ="video.hidden = !video.checked"/>

It introduces a video.checked property that's the opposite of video.hidden and as soon as the checkbox changes the values are updated.

Example here: http://plnkr.co/edit/7HYht6ubkUUJQ0r3g16m?p=preview