从 DOM 中删除违规元素时,无效的表单不会重新计算其有效性

Posted

技术标签:

【中文标题】从 DOM 中删除违规元素时,无效的表单不会重新计算其有效性【英文标题】:An invalidated form does not get its validity recalculated when offending element removed from DOM 【发布时间】:2013-01-02 17:07:42 【问题描述】:

我在我的一个指令中使用ctrl.$setValidity 使表单无效。但是,有一种方法存在于其他地方,可以在不同的条件下从 DOM 中删除该元素。当我使用 $setValidity 使表单无效然后删除有问题的元素时会发生什么情况是表单仍然无效,而我希望它做的是根据其新的 Inputfield 集重新计算其有效性。

请注意,我不只是在寻找ctrl.$setValidity true,(表单中的其他输入字段可能有效也可能无效),我只是希望重新计算表单。

以下是指令代码:

 app.directive('dir', function() 
   return 
      restrict: 'C',
      require: "ngModel",
      link: function(scope, element, attrs, ctrl) 
      someValue = scope.someValue;
      someField = element.find(".some-class");

      monitorField = function(newValue, oldValue)
        console.log("whee");
        scope.someFunction(newValue);
        if(newValue =="Invalidate NOW!")
        ctrl.$setValidity('someClass', false);      
         else if (oldValue == "Invalidate NOW!")
           angular.element(document.querySelector('.some-class')).remove();
         else 
          ctrl.$setValidity('someClass', true); 
       
      
    scope.$watch("someValue", monitorField, true);
   
 
);

作用于:

   <FORM class="dir" ng-model="someValue">
     <INPUT type="text" class="some-class" ng-model="someValue"/>
     <INPUT type="text" class="some-other-class"/>
   </FORM>

(是的,这是一个有点做作的例子)。

问题在这里复现:http://jsfiddle.net/kTuAY/

在我的实际代码中,我根据对象数组填充输入字段,这些对象通过 Service 填充,并通过 array.splice 删除元素。 jsfiddle 中给出的示例只是为了简单起见。

在这个小提琴中可以看到另一个有趣的失败情况:

http://jsfiddle.net/JACAv/

具体来说,如果其中一个输入依赖于值与另一个不冲突,因此无效,然后其他字段值更改后,有效性仍然不正确。

我上班时暂时不能正常工作的小提琴:

http://jsfiddle.net/yQpHL/

谢谢!

【问题讨论】:

看来我偶然发现了一个真正诚实的 Angular JS 错误。 (github.com/angular/angular.js/issues/1572) 任何解决方法的建议都表示赞赏 【参考方案1】:

如果可能的话,应该通过删除模型指向的 $scoped 对象的一部分来删除链接到模型的一部分的 DOM 元素。想想基本的 ngRepeat,如果你从数组中删除一个项目,它就会从 DOM 中删除一个元素。您的表单应该以相同的方式工作。这将防止这种情况发生。所有验证信息都包含在 $scope 中的模型中...例如:$scope.myForm.model.$error.required。如果您删除模型指向的 $scope 属性或数组项(例如 $scope.foo$scope.items.splice(2,1),Angular 将知道(大概在下一个 $digest 期间)删除模型以及它的验证信息。

无论是在控制器还是指令中删除项目都没有关系,但它应该从 $scope 中删除,然后需要进行摘要以更新 UI 和验证。

我希望这会有所帮助,因为我觉得我在想说的话时磕磕绊绊......

【讨论】:

谢谢!但是,我实际上已经在控制器范围内的数组上使用 .splice 来填充/取消填充我的表单。那并没有解决问题(我已经编辑了我的原始帖子以澄清)。知道如何获取表单 model.$error 非命名表单的信息(通过 Rails 助手生成)吗? 在我看来,如果您不得不使用代码手动删除 DOM 元素,您的模型并不能准确地反映您想要在视图中显示的内容。 MVC 架构的全部目的是你有一个模型,如果你改变它,改变就会反映在你的视图中。此外,验证就是验证,它不应该改变模型或视图,而是显示一些验证消息。它不应该做任何比验证更多的事情。 ...我认为这里真正的问题是设计可能存在缺陷。 如果我之前的评论听起来像说教,那我很抱歉,这不是我想要的。我只是想让你退出你的设计并重新评估它,因为我真的看不出你在做什么,但听起来你使用架构的方式有问题。 这很公平。我将在今天晚些时候整理一个更广泛的 jsfiddle 我似乎找到了一个 hacky 解决方案。不过感谢您的帮助!很有启发性【参考方案2】:

我认为问题不在于指令,而在于“其他”代码

如果 DOM 元素移除是在响应“本机”事件时完成的 - 即拖放事件、ajax 事件或其他事件,则 Angular 框架将不知道自己运行重新计算。在此代码中,完成更新后,您必须在适当的范围内调用 $scope.$apply()。

哦,我看到了你的小提琴......好的。让生活更轻松一些怎么样 - 与其删除你的 DOM 元素,为什么不用 ng-show 属性来装饰它,让 Angular 来处理其余的事情?

【讨论】:

【参考方案3】:

我最终解决这个问题的方法是:

    ng-repeated 的数组上放置一个单独的监视表达式。 在该监视表达式中,构造当前存在于与相关指令匹配的输入字段中的所有值的哈希。 使用.val() 强制重置这些字段的值,然后在内部字段上调用$compile 以重新触发内部监视表达式 在函数内添加控制器上的输入字段值(在内部指令ctrl.$setValidity 之前调用的函数: 验证他输入值的次数是1 重置被 ng 重复的数组以包含哈希值

好处:

当一个元素被删除时,外部监视(整个数组上的那个)触发,重新计算哈希值,重置每个字段的值并重新验证 当检测到碰撞时,它会正确冒泡,将所有碰撞字段标记为无效,当消除碰撞时,重新验证所有字段 坏处: 每次数组更改时对每个字段调用 $compile 计算效率低下 由于内部 watch 表达式会在值变化时触发一次,而外部 watch 表达式调用 compile 时会触发一次,因此$digest 的数量实际上增加了一倍

我会在清理代码后立即整理一个代码示例。

【讨论】:

以上是关于从 DOM 中删除违规元素时,无效的表单不会重新计算其有效性的主要内容,如果未能解决你的问题,请参考以下文章

尝试从 API 中列出数据会给出错误消息“不变违规:元素类型无效:........”

使用javascript修改dom时,我该如何做到这一点,在重新加载我的网页时不会删除这些

有没有办法为在刷新或 DOM 位置发生变化时不会获得无效元素的元素生成唯一选择器?

不变违规:当元素在 DOM 中时,目标容器不是 DOM 元素

不变违规:元素类型无效 - React Native

不变违规:元素类型无效:预期为字符串(用于内置组件)