Search code examples
angularjsangularjs-directiveng-showng-hide

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


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>

Solution

  • Below is my best attempt. I still use jquery to detect if an element is visible, and the directive does not use an isolated scope but at least all the logic is contained in two directives which enables re-use in other projects:

    The directive code (clearmModelWhenHidden.js)

    angular.module('clearModelWhenHidden', [])
    
    .directive('clearModelWhenHiddenContainer', function() {
    
        return {
          scope: false,
          controller: function($scope, $parse, $timeout) {
    
            $scope.registeredElements = [];
    
            //since we dont' have an isolate scope, namespace our public API to avoid collision
            this.clearModelWhenHidden = {};
    
            //to share a method with child directives use the "this" scope and have children require the parent controller... 
            this.clearModelWhenHidden.register = function(e) {
              $scope.registeredElements.push(e);
    
            }
    
            $scope.$watchCollection(
    
              function() {
                //convert the registered elements ng-model attribute from a string to an angular
                //object that can be watched for changes
                var parsedArray = [];
                angular.forEach($scope.registeredElements, function(item, i) {
                  parsedArray.push($parse(item.attributes.ngModel)($scope))
                });
                return parsedArray;
              },
              function(newvalue) {
    
                $timeout(function() {
                  angular.forEach($scope.registeredElements, function(item, i) {
    
                    var isVisible = $(item.element).is(':visible');
    
                    if (!isVisible) {
    
                      var value = $parse(item.attributes.ngModel)($scope);
    
                      //create a string that sets the ng-model of each element to an empty string,
                      //for example, person.name=''
                      var stringToEval = item.attributes.ngModel + '=""  ';
    
                      console.log(stringToEval)
    
                      $parse(stringToEval)($scope);
                    }
                  })
                });
              }
            );
          }
        }
      })
      .directive('clearModelWhenHidden', function() {
        var link = function(scope, element, attributes, parentController) {
          //pass in the element itself so we can used jquery to detect visibility and the attributes so the container can create a watch on the models
          parentController.clearModelWhenHidden.register({
            'element': element[0],
            'attributes': attributes
          });
        }
        return {
          link: link,
          require: '^clearModelWhenHiddenContainer'
        }
      })

    and a demo page

    <!doctype html>
    <html xmlns:ng="http://angularjs.org" ng-app="app">
    
    <head>
      <meta http-equiv="X-UA-Compatible" content="IE=Edge">
    
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.min.js" type="text/javascript"></script>
      <script language="javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.js"></script>
      <script language="javascript" src="clearModelWhenHidden.js"></script>
    </head>
    
    <body ng-controller="MainCtrl as MainCtrl">
    
      <div style="padding:20px; background-color:silver;color:blue">{{MainCtrl.person | json }}</div>
    
      <div clear-model-when-hidden-container>
    
        <section>
    
          Name:
          <input ng-model="MainCtrl.person.name" clear-model-when-hidden>
    
          <div ng-show="MainCtrl.person.name.length">
            <label>Age</label>:
            <input ng-model="MainCtrl.person.age" clear-model-when-hidden>
    
            <section ng-if="MainCtrl.person.age.toString().length">
              <label>Hobby</label>:
              <input ng-model="MainCtrl.person.hobby" clear-model-when-hidden>
            </section>
          </div>
        </section>
      </div>
    
    </body>
    
    </html>