Douglas Gaskell Douglas Gaskell - 3 months ago 42
AngularJS Question

Clearing directive scope variables on $destroy

On my radio buttons, I have a directive that binds to the

change
event and sets the
ngModel
defined on the element with the radio buttons value. This works fine.

However, the radio buttons can be hidden removed from the DOM based on the user's selection and I want to clear those variables in my controller when this happens. I have a handler for the
$destroy
event in which I set the scopes
ngModel
value to
undefined
.

For some reason setting the scopes value in
$destroy
is not being changed in my controller while setting it in the
change
handler does.

HTML:

<div ng-repeat="item in category.items" class="field">
<div class="ui radio checkbox">
<input type="radio" value="{{item}}" sm-radio-button ng-model="submissions[category.model]" name="{{category.name}}" checked="" tabindex="0" class="hidden">
<label ng-bind="item"></label>
</div>
</div>


Directive:

.directive('smRadioButton', function(){
return {
scope: {
ngModel: '='
},
link: function(scope, element, attrs){

//Instantiate checkbox on load and set value to undefined
element.parent().checkbox();
scope.ngModel = undefined;


element.on('$destroy', function(){
scope.ngModel = undefined;
})

element.on('change', function(e){
scope.$apply(function(){
scope.ngModel = attrs.value;
});
});
}
};
})


Where
category
is one of many config objects like:

{
name: 'Type',
model: 'type',
alwaysShow: true,
items: ['Spending','Bills','Account Transfer','Deposit']
},


Note: I am not using
scope.$apply
in
$destroy
because there is already a digest cycle in progress.
$destroy
is actually being triggered.

Edit: Tested this on a few other elements, it seems that in
$destroy
only what appears to be a local scope of that variable is changed? If I reference directly to the controller through
scope.$parent
the controller variable does change. (This is not ideal since this is several ng-repeats deep which means chaining a bunch of
$parent
s)

Answer

You should use NgModelController methods instead.

Taken from the NgModelController documentation:

NgModelController provides API for the ngModel directive. The controller contains services for data-binding, validation, CSS updates, and value formatting and parsing. It purposefully does not contain any logic which deals with DOM rendering or listening to DOM events. Such DOM related logic should be provided by other directives which make use of NgModelController for data-binding to control elements. Angular provides this DOM logic for most input elements. At the end of this page you can find a custom control example that uses ngModelController to bind to contenteditable elements.

You should use $setViewValue and then, as highlighted above, due to the nature of the API you need to call $render to update the view:

.directive('smRadioButton', function(){
    return {
        scope: {
            ngModel: '='
        },
        link: function(scope, element, attrs, ctrl){

            //Instantiate checkbox on load and set value to undefined
            element.parent().checkbox();

            // set the view value
            ctrl.$setViewValue(undefined);

            // render the changes
            ngModel.$render();

            element.on('$destroy', function(){
              ctrl.$setViewValue(undefined);
              ngModel.$render();
            });

            element.on('change', function(e){
                ctrl.$setViewValue(attrs.value);
                ngModel.$render();
            });

        }
    };
});