Best way to clear model values when the field is hidden in angular

gabriel picture gabriel · Jul 28, 2015 · Viewed 12.6k times · Source

Below is some code to clear angular model values when the corresponding input to the model is hidden via ng-show, using classnames and jquery, but it has a bad smell bc it manipulates DOM in controller (Edit- it doesn't manipulate the DOM it changes the scope model values, but i am not crazy about using jquery) . Is there an "angular way" to do this?

I should add that the code below is just for a proof of concept to show that a solution is possible. The actual project has very complicated business rules to show sections, sub-section and sub-sections etc that have many logical branches... so it would be difficult to code that logic in the watch as @New Dev suggests... in addition, I would not want to have the logic in two places: both in all the divs that have show and hide AND in a function ...

    <!doctype html>
<html  xmlns:ng="http://angularjs.org" ng-app="app">
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=Edge">  


</head>

<body ng-controller="MainCtrl">

    <div style="padding:20px; background-color:silver;color:blue">{{person | json }}</div>  

    Name: <input ng-model="person.name" name="name" >

    <div ng-show="person.name.length">

        Age: <input ng-model="person.age" name="age" class="hide-clear">

        <div ng-show="person.age.toString().length">
            Hobby: <input ng-model="person.hobby" name="hobby" class="hide-clear">
        </div>

    </div>

    <Script>

        angular.module('app', [])

        .controller('MainCtrl', function($scope,$log,$timeout){             

            $scope.person = {
                name: 'mr smith',
                age: 51,
                hobby: 'coding'                 
            }   

            $scope.$watchCollection(
                //return the value to be watched
                function($scope){ 
                    return $scope.person
                },
                //function to be called when changed
                function(newValue,oldValue){
                    $timeout( function() {  
                        $(".hide-clear").each(function(){
                            var t = $(this);                            
                            if( !  t.is(":visible") ) {
                                $scope.person[t.attr('name')] = '';
                            }
                        })

                    })
                }               
            )           
        })

    </Script>
</body>
</html>

Answer

New Dev picture New Dev · Jul 28, 2015

I'm glad that you recognized the approach above as a poor design (or "bad smell", as you put it). Indeed, an Angular way (or more generally, an MVVM way) would be to only manipulate the View Model, and let the View Model drive the View.

For example, you are attempting to set $scope.person.age = "" and $scope.person.hobby = "" when their parent container is hidden with ng-show="person.name.length" (i.e. when $scope.person.name is empty). Instead of using the resulting invisibility of the container as an indicator, use the original data that caused the container to be invisible in the first place.

$scope.$watch("person.name", function(val){
  if (val === "") { // or, if (!val.length), to make it completely equivalent
    $scope.person.age = "";
    $scope.person.hobby = "";
  }
});

The code above watches for $scope.person.name to be empty (and/or undefined, whatever your definition is) to set the other properties. It doesn't matter at all to the controller how the View reacted to empty person.name at all - it could have done some animation or other UI tricks. The logic only deals with the View Model state.

The code above could be further improved to avoid a $watch and instead react to the event that caused $scope.person.name to become empty. From your example it appears to be only caused by the user deleting the name from the textbox.

<input ng-model="person.name" ng-change="onPersonChanged()">
$scope.onPersonChanged = function(){
   if (!$scope.person.name) {
      $scope.person.age = "";
      $scope.person.hobby = "";
   }
};

This is preferable to $watch since $watch fires on every digest cycle, whereas ng-change fires only when there is a change to the input field.