当字段隐藏在 angular 中时清除模型值的最佳方法

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

下面是一些代码,用于清除 angular 模型值,当通过 ng-show 隐藏模型的相应输入时,使用类名和 jquery,但它有难闻的气味,因为它操纵DOM 在控制器中(编辑 - 它不会操纵 DOM 它会更改范围模型值,但我并不热衷于使用 jquery)。有 "angular way" 可以做到这一点吗?

我应该补充一点,下面的代码只是为了概念验证,以表明解决方案是可行的。实际项目有非常复杂的业务规则来显示具有许多逻辑分支​​的部分、子部分和子部分等......所以很难按照@New Dev建议的那样在手表中编写逻辑......另外,我不希望在两个地方有逻辑:在所有显示和隐藏的 div 中以及在一个函数中......

    <!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>

很高兴您将上述方法视为糟糕的设计(或 "bad smell",如您所说)。实际上,Angular 方式(或更一般地,MVVM 方式)将仅操纵视图模型,并让视图模型驱动视图。

例如,您试图设置 $scope.person.age = ""$scope.person.hobby = "" 时它们的父容器被 ng-show="person.name.length" 隐藏(即当 $scope.person.name 为空时)。不要使用容器的不可见性作为指标,而是首先使用导致容器不可见的原始数据。

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

上面的代码监视 $scope.person.name 为空(and/or undefined,无论您的定义是什么)以设置其他属性。 View 对空 person.name 的反应对控制器来说根本不重要——它可以做一些动画或其他 UI 技巧。该逻辑仅处理视图模型状态。

上面的代码可以进一步改进以避免 $watch 并改为对导致 $scope.person.name 变空的事件做出反应。从您的示例来看,这似乎只是由于用户从文本框中删除了名称所致。

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

这优于 $watch,因为 $watch 在每个摘要周期触发,而 ng-change 仅在输入字段发生更改时触发。

以下是我的最佳尝试。我仍然使用 jquery 来检测元素是否可见,并且该指令不使用隔离范围,但至少所有逻辑都包含在两个指令中,可以在其他项目中重复使用:

指令代码(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'
    }
  })

和一个演示页面

<!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>