Javascript 在表单级别使用$setValidity后,validity被固定为false

Javascript 在表单级别使用$setValidity后,validity被固定为false,javascript,angularjs,forms,validation,Javascript,Angularjs,Forms,Validation,我正在用Angular编写一个相当复杂的搜索表单 表单分为多个部分-您可以按ID、url、文本字符串或位置进行搜索。当您同时按文本字符串和位置进行搜索时,我需要验证您是否同时提交纬度和经度 换句话说,通过文本字符串进行搜索是可以的。通过lat/lng进行搜索是可以的。按文本字符串和纬度搜索,但忽略经度是不正确的 只要有可能,我都会使用HTML5和angular指令来验证各个字段的内容,但我试图通过使用范围观察器、查看表单对象和使用$setValidity()来验证特定的值组合如果我发现当前搜索模

我正在用Angular编写一个相当复杂的搜索表单

表单分为多个部分-您可以按ID、url、文本字符串或位置进行搜索。当您同时按文本字符串和位置进行搜索时,我需要验证您是否同时提交纬度和经度

换句话说,通过文本字符串进行搜索是可以的。通过lat/lng进行搜索是可以的。按文本字符串和纬度搜索,但忽略经度是不正确的

只要有可能,我都会使用HTML5和angular指令来验证各个字段的内容,但我试图通过使用范围观察器、查看表单对象和使用
$setValidity()来验证特定的值组合
如果我发现当前搜索模式与特定字段组合不兼容

我当前的问题是,一旦我使用了
$setValidity()
一次,验证状态就会“卡住”。当用户切换出
'textOrLocation'
搜索模式时,我想让angular返回其默认的验证行为。我不明白为什么它不这样做-我只在检查表单处于
'textOrLocation'
模式后,在范围更改时调用
$setValidity()

Javascript:

$scope.search = {
    mode: 'id'
};

$scope.$watch(textOrLocationValid, function() {});

function textOrLocationValid() {
    var usingTextOrLocation = $scope.search.mode == 'textOrLocation';
    if (usingTextOrLocation) {
        var textModel = $scope.form.searchText || {},
            textValid = textModel.$valid,
            textValue = textModel.$modelValue,
            latModel = $scope.form.searchLat || {},
            latValid = latModel.$valid,
            latValue = latModel.$modelValue,
            lngModel = $scope.form.searchLng || {},
            lngValid = lngModel.$valid,
            lngValue = lngModel.$modelValue,
            formValid = (textValid && latValid && lngValid) && // No invalid fields
            ((latValue && 1 || 0) + (lngValue && 1 || 0) != 1) && // Either both lat and long have values, or neither do
            (textValue || latValue); // Either text or location are filled out
        if (formValid) {
            // Explicitly set form validity to true
            $scope.form.$setValidity('textOrLocation', true);
        } else {
            // Explicitly set form validity to false
            $scope.form.$setValidity('textOrLocation', false);
        }
    }
}
HTML

<form name="form">
    <div ng-if="search.mode == 'id'">
        <input type="text" name="searchId" required>
    </div>
    <div ng-if="search.mode == 'textOrLocation'">
        <input type="text" name="searchText">
        <input type="number" name="searchLat" min="-90" max="90" step="0.000001">
        <input type="number" name="searchLng" min="-180" max="180" step="0.000001">
    </div>
    <button ng-disabled="form.$invalid">Submit</button>
</form>

提交

我的理解是,因为函数正在被监视,所以在每个摘要期间,它实际上是通过角度周期性地进行评估的。一个简单的解决方案可能是在特定表单未处于焦点时将textOrLocation的有效性设置为true。这将允许按钮状态取决于id表单中字段的有效性

function textOrLocationValid() {
    var usingTextOrLocation = $scope.search.mode == 'textOrLocation';
    if (usingTextOrLocation) {
        var textModel = $scope.form.searchText || {},
            textValid = textModel.$valid,
            textValue = textModel.$modelValue,
            latModel = $scope.form.searchLat || {},
            latValid = latModel.$valid,
            latValue = latModel.$modelValue,
            lngModel = $scope.form.searchLng || {},
            lngValid = lngModel.$valid,
            lngValue = lngModel.$modelValue,
            formValid = (textValid && latValid && lngValid) && // No invalid fields
            ((latValue && 1 || 0) + (lngValue && 1 || 0) != 1) && // Either both lat and long have values, or neither do
            (textValue || latValue); // Either text or location are filled out
        if (formValid) {
            // Explicitly set form validity to true
            $scope.form.$setValidity('textOrLocation', true);
        } else {
            // Explicitly set form validity to false
            $scope.form.$setValidity('textOrLocation', false);
        }
    }
    else{
        // Explicitly set form validity to true because form is not active
        $scope.form.$setValidity('textOrLocation', true);
    }
}

我让它工作了,你可以在这里找到代码

有几个问题(我假设,您尝试在控制器中处理表单,而不是在自定义指令中):

  • 我们试图在controller中访问的
    $scope.form
    与视图中的表单不同。表单有自己的作用域。无法在控制器中直接访问。要解决这个问题,我们可以将
    表单
    附加到控制器中声明的
    $scope.forms
    -object(阅读有关继承模式的更多信息)
  • 我们应该将
    ng model
    附加到输入,这样我们就可以$watch它们,因为不可能直接查看表单(请阅读更多)
  • 此外,我们还必须观察
    $scope.search
    的变化

  • 这绝对不是处理自定义验证的最优雅的解决方案。。。我们将尝试为此制定自定义指令。

    要创建复杂的测试,您可以使用一个小指令,这在将来可能对您有用

    使用此指令,您可以编写:

    <div ng-form="myForm">
      <div>
        <input type="text" ng-model="searchText" name="searchText">
        <input type="number" ng-model="searchLat" name="searchLat" min="-90" max="90" step="0.000001">
        <input type="number" ng-model="searchLng" name="searchLng" min="-180" max="180" step="0.000001">
        <span use-form-error="textOrLocation" use-error-expression="textOrLocationValid()" use-error-input="myForm"></span>
      </div>
      {{myForm.$error}}
      <br>
      <button ng-disabled="myForm.$invalid">Submit</button>
    </div>
    
    }))


    现场示例:

    谢谢!这正是我所缺少的。我不理解“validationErrorKey”(由
    $setValidity
    获取的第一个参数)的用途。我认为使用
    表单。$setValidity()
    使用
    true
    作为第二个参数将强制窗体的
    $valid
    状态为true。这是不正确的。相反,它将分配给“textOrLocation”错误键的值设置为true,允许所有其他验证指令正常运行。
    angular.module('ExampleApp', ['use']).controller('ExampleController', function($scope) {
    $scope.search = {
      mode: 'textOrLocation'
    };
    $scope.textOrLocationValid = function() {
      var usingTextOrLocation = $scope.search.mode == 'textOrLocation';
      if (usingTextOrLocation) {
        var textModel = $scope.myForm.searchText || {},
          textValid = textModel.$valid,
          textValue = textModel.$modelValue,
          latModel = $scope.myForm.searchLat || {},
          latValid = latModel.$valid,
          latValue = latModel.$modelValue,
          lngModel = $scope.myForm.searchLng || {},
          lngValid = lngModel.$valid,
          lngValue = lngModel.$modelValue,
          formValid = (textValid && latValid && lngValid) && // No invalid fields
          ((latValue && 1 || 0) + (lngValue && 1 || 0) != 1) && // Either both lat and long have values, or neither do
          (textValue || latValue); // Either text or location are filled out
        return !formValid;
      } else {
        // Explicitly set form validity to true because form is not active
        return false;
      }
    }