【问题标题】:How to correctly control the $dirty and $pristine state of an Angular directive如何正确控制 Angular 指令的 $dirty 和 $pristine 状态
【发布时间】:2015-03-18 14:50:08
【问题描述】:

我正在尝试创建一个角度指令,该指令将组成一个 KendoUI TreeView 控件和一个文本输入,以便它搜索输入的文本并选择项目。我有这个功能,但是我希望指令在表单中表现良好,以便在更改复选框值时设置表单的脏属性。

不幸的是,刚才的行为是,当有人在输入中输入文本时,from 被设置为脏(不是所需的行为)并且选中复选框没有任何效果。

我希望在表单上调用 $setDirty(),但它对我拥有的函数不可见,我希望有一种干净的方法使它可见而不是将其添加到 $scope,这也不会t 解决 $dirty 设置为 true 但也输入搜索文本的问题。

我创建了一个Plunk 来演示这个问题。

这是指令的代码:

var serviceRoot = "http://demos.telerik.com/kendo-ui/service";
 angular.module("KendoDemos", ["kendo.directives"])
   .controller("MyCtrl", function($scope, $http) {
     $scope.treeData = new kendo.data.HierarchicalDataSource({
       data: [{
         text: "Cat"
       }, {
         text: "Dog",
         items: [{
           text: "Fido"
         }, {
           text: "Rover"
         }]
       }, {
         text: "Rabbit",
         checked: true
       }]
     });
   });

 (function() {
   'use strict';


   var app = angular.module('KendoDemos');

   var template = '<div>	<div class="input-group">		<input type="text" ng-click="textNotFound=false"			   class="form-control" placeholder="Find node"			   ng-model="searchText" ng-enter="search()" ng-esc="searchText=\'\';textNotFound = false;">		<div class="input-group-btn">			<span class="btn btn-default" ng-click="search()"><span class="fa fa-search clickable"				style="font-size: 14px; height: 18px"></span></span>		</div>	</div>	<div id="treeview" kendo-tree-view="searchTree" k-data-source="dataSource" k-load-on-demand="false" k-on-check="onCheck(kendoEvent)"	      k-options="{checkboxes:true }">		<span k-template>{{dataItem.text}}</span>	</div></div>'

   app.directive('searchableTree', function() {
     //Usage:
     //<div data-searchable-tree ng-model="vm.treeData"></div>
     var directive = {
       template: template,
       require: '?^form',
       replace: true,
       transclude: true,
       scope: {
         'dataSource': '=ngModel',
         'controlId': '@id'
       },
       restrict: 'AE',
       link: function($scope, element, attrs, formCtrl) {
         $scope.search = function(id) {
           var tree = $scope.searchTree;
           var node = tree.findByText($scope.searchText);
           tree.expandTo($scope.searchTree.dataItem(node));
           tree.select(node);
           tree.dataItem(node).set("checked", true);
           //var checkbox = $(node).find(":checkbox");
           //checkbox.prop("checked", true);
         }
         $scope.setSelected = function(id) {
           alert(id);
         }
         $scope.onSelect = function(id) {
           alert(id);
         }
         $scope.onCheck = function(e) {
           var checkbox = $(e.node).find(":checkbox");
           var checked = checkbox.prop("checked");
           //updateValidity(e.node, checked);
         }
       }
     };
     return directive;
   });

   app.directive('ngEnter', function() {
     return function(scope, element, attrs) {
       element.bind("keydown keypress", function(event) {
         if (event.which === 13) {
           scope.$apply(function() {
             scope.$eval(attrs.ngEnter);
           });

           event.preventDefault();
         }
       });
     };
   });

   app.directive('ngEsc', function() {
     return function(scope, element, attrs) {
       element.bind("keydown keypress", function(event) {
         if (event.which === 27) {
           scope.$apply(function() {
             scope.$eval(attrs.ngEsc);
           });

           event.preventDefault();
         }
       });
     };
   });

 })();
/* Styles go here */

html {
  font-size: 12px;
  font-family: Arial, Helvetica, sans-serif;
}
#example {
  text-align: center;
}
.demo-section {
  display: inline-block;
  vertical-align: top;
  width: 320px;
  height: 300px;
  text-align: left;
  margin: 0 2em;
}
.clickable {
  cursor: pointer;
}
<link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1411/styles/kendo.common.min.css" />
<link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1411/styles/kendo.default.min.css" />
<link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1411/styles/kendo.dataviz.min.css" />
<link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1411/styles/kendo.dataviz.default.min.css" />
<link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1411/styles/kendo.default.mobile.min.css" />
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">

<script src="http://cdn.kendostatic.com/2014.3.1411/js/jquery.min.js"></script>
<script src="http://cdn.kendostatic.com/2014.3.1411/js/angular.min.js"></script>
<script src="http://cdn.kendostatic.com/2014.3.1411/js/kendo.all.min.js"></script>

<body>
  <div id="example" ng-app="KendoDemos" ng-controller="MyCtrl">
    <form name='myForm'>
      <div name='myTree' data-searchable-tree ng-model="treeData"></div>
    </form>
    <hr/>

    <div>Directive is {{myForm.myTree.$dirty ? 'dirty' : 'pristine'}}</div>
    <hr/>
    <div>Form is {{myForm.$dirty ? 'dirty' : 'pristine'}}</div>
  </div>


</body>

【问题讨论】:

    标签: javascript angularjs angularjs-directive kendo-ui


    【解决方案1】:

    2/8/2016 附录:写这篇文章后,我意识到 scope 方法仅在调试模式下可用。我不建议将此用于生产代码。

    我有一个类似的问题,实际上想出了一个非常简单的解决方案来解决它。如果您有一个元素的引用,则可以使用 scope() 方法来查找它存在的范围。鉴于此,此解决方案将起作用:

      function dirtyParent(uid)
        {
          var formEl = angular.element(angular.element('#' + uid).closest('form'));
    
          var formScope = formEl.scope();
    
          var formName = formEl.attr('name');
    
          formScope[formName].$setDirty();
        }
    

    这个函数从 DOM 中获取元素,找到它最近的祖先是一个表单,获取该元素的名称和范围,然后在范围上找到表单控制器(它与 @ 共享它的名称987654323@ 表单的属性)并在其上调用$setDirty() 方法。这样,您的控制器将被设置为 $dirty 状态。

    在 New Dev 的回答中,您似乎可以使用 $formController 信息跳过 DOM 遍历,但我在编写此代码时并没有意识到这一点。

    【讨论】:

      【解决方案2】:

      这里发生了一些事情,因此无需详细说明您的特定指令,您应该了解以下内容:

      ng 模型:

      您应该require: "ngModel" 而不是将其用作范围变量。 ng-model 是一个指令,您可以通过它与其他表单和验证器正确集成。 ng-model 故意与 DOM 无关,应用作模型和视图值之间的概念管道。

      如果你这样做得当,你就不需要require: "form"

      您有一个自定义输入控件:

      认识到您正在有效地构建自定义输入指令。它以某种方式设置模型,并以某种方式呈现 UI。

      查看 Angular 文档中关于创建自定义输入指令的 this example

      在自定义指令的模板中使用 ng-model:

      现在,您看到的 $dirty 的问题是因为您的指令 (ng-model="searchText") 中的 ng-model 与外部表单集成,完全不知道它是另一个自定义输入控件的一部分。

      ngModel 指令在 DOM 层次结构中搜索(参见 src)表单指令,并最终找到您的指令所在的表单。

      解决此问题的一种方法是不要在模板中使用ng-model,而是使用element.on('input')(或其他方式)来检测更改,并适当地设置“必需”ngModel 的视图值。

      另一个太“欺骗”ngModel 相信表单控制器是空的,所以它不会更新它。

      这很容易做到 - 但我无法评论这是否是面向未来的。在指令的pre-link 中执行以下操作:

      link: {
        pre: function(scope, element){
           // this will trip the search, and apply a `nullFormCtrl` internally, 
           // which doesn't do anything.
           element.data("$formController", null); 
        }
      }
      

      【讨论】:

      • 我试图要求 ngModel 而不是创建范围变量,但最初没有运气,它只是破坏了我与 TreeView 控件的绑定。我已经看过 Angular 文档,但老实说,他们的例子太做作了,并没有真正阐明这个主题
      • @Mark,您仍应在 TreeView 指令上使用 ng-model。和require没有关系
      • 这里是更新的链接:plnkr.co/lkGQFi 它仍然没有更新指令。如果我从范围中删除 dateSource 并将引用从 dataSource 更改为 ngModel 它根本不起作用
      • “不更新指令”是什么意思?它不会更新表单的脏/原始状态,但之前也没有 - 这是你想要的吗?
      • 如果我选中或取消选中一个复选框,表单的 $dirty 属性仍然不受影响
      猜你喜欢
      • 2015-12-21
      • 1970-01-01
      • 2023-03-21
      • 1970-01-01
      • 2015-07-05
      • 2016-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多