I have written a custom AngularJS directive that implements a toggle checkbox based on Bootstrap Toggle. I added support for angular-translate, but that is beyond my actual proplem. Furthermore, I wanted to use angular-cookies to save and restore the current state of a particular checkbox.
However, my directive does not pick-up the initial value of the data model properly.
This is my directive:
app.directive('toggleCheckbox', ['$rootScope', '$translate', '$timeout', function($rootScope, $translate, $timeout) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attributes, ngModelController) {
// Change model, when checkbox is toggled
element.on('change.toggle', function(event) {
var checked = element.prop('checked');
console.log('change.toggle was called: ' + checked + ' vs. ' + ngModelController.$viewValue);
if (checked != ngModelController.$viewValue) {
scope.$apply(function changeViewModel() {
ngModelController.$setViewValue(checked);
console.log('change.toggle:', checked);
});
}
});
// Render element
ngModelController.$render = function() {
element.bootstrapToggle(ngModelController.$viewValue ? 'on' : 'off')
};
// Translate checkbox labels
var updateLabels = function() {
var offLabel = (attributes['off'] ? $translate.instant(attributes['off']) : 'Off');
var onLabel = (attributes['on'] ? $translate.instant(attributes['on']) : 'On');
angular.element(document).find('label.btn.toggle-off').html(offLabel);
angular.element(document).find('label.btn.toggle-on').html(onLabel);
};
// Update labels, when language is changed at runtime
$rootScope.$on('$translateChangeSuccess', function() {
updateLabels();
});
// Initialize labels for the first time
$timeout(function() {
updateLabels();
});
// Clean up properly
scope.$on('$destroy', function() {
element.off('change.toggle');
element.bootstrapToggle('destroy');
});
// Initialize element based on model
var initialValue = scope.$eval(attributes.ngModel);
console.log('initialValue:', initialValue);
element.prop('checked', initialValue);
}
};
}]);
This is how I initialize the data model from cookie:
mainController.controller('MainCtrl', ['$scope', '$cookies', 'Main', function($scope, $cookies, Main) {
this.$onInit = function() {
$scope.settings.foobar = $cookies.get('foobar');
console.log('$onInit(): ', $scope.settings.foobar);
};
// ...
}]);
And this is how I eventually use my directive:
<div id="foobar-switcher" ng-if="isAdmin()">
<label for="foobar_toggle"><span translate="foobar"></span>:</label>
<input id="foobar_toggle" type="checkbox"
ng-model="settings.foobar" ng-change="setFoobarCookie(settings.foobar)" toggle-checkbox
data-off="foo_label" data-offstyle="success"
data-on="bar_label" data-onstyle="danger" />
</div>
Ultimately, I get this debug output:
controllers.js:33 $onInit(): true
directives.js:76 initialValue: true
directives.js:37 change.toggle was called: false vs. false
So in case the value true
is stored in the cookie, the model gets initialized properly in the global scope and even the directive uses the correct value to initialize element
. The change.toggle
event handler is triggered as well, but there the element's value is now false
.
Why is that and how can I fix this problem?
It turned out that $cookies.get('foobar')
returns a String and the checkbox cannot handle a ng-model
of that type. Instead, the cookie value must be evaluated such, that a boolean value can be set in the data model:
var cookie = $cookies.get('foobar');
$scope.settings.foobar = (cookie === 'true');
The checkbox is then correctly initialized on page load.