使用 AngularJS 跳过嵌套表单验证

Skip nested forms validation with AngularJS

本文关键字:表单 验证 嵌套 AngularJS 使用      更新时间:2023-09-26

如何使用AngularJS跳过嵌套表单的验证?即使外部表单无效,我也必须使外部表单有效。

在下面的示例中,外部形式应该是有效的(fOuter.$valid必须为真)。默认情况下,它不是。有没有选择?

代码(jsFiddle):

<div ng-app ng-controller="Ctrl">  
    <ng-form name="fOuter">  
        <h3>Outer form (valid={{fOuter.$valid}})</h3>  
        <input type="text" name="txtOuter" ng-model="outer" placeholder="(required)" required />  
        <ng-form name="fInner">  
            <h3>Inner form (valid={{fInner.$valid}})</h3>  
            <input type="text" name="txtInner" ng-model="inner" placeholder="(required)" required />  
        </ng-form>  
    </ng-form>  
</div>

这是我受mbernath启发的解决方案,它将形式本身与其父亲完全隔离开来。

此解决方案负责:

  • 表单有效期($valid、$invalid)
  • 表单交互($pristine、$dirty)
  • 嵌套表单的有效性和交互

在此 JSFiddle 中查看它的实际效果。

angular.module('isolateForm',[]).directive('isolateForm', [function () {
    return {
        restrict: 'A',
        require: '?form',
        link: function (scope, elm, attrs, ctrl) {
            if (!ctrl) {
                return;
            }
            // Do a copy of the controller
            var ctrlCopy = {};
            angular.copy(ctrl, ctrlCopy);
            // Get the parent of the form
            var parent = elm.parent().controller('form');
            // Remove parent link to the controller
            parent.$removeControl(ctrl);
            // Replace form controller with a "isolated form"
            var isolatedFormCtrl = {
                $setValidity: function (validationToken, isValid, control) {
                    ctrlCopy.$setValidity(validationToken, isValid, control);
                    parent.$setValidity(validationToken, true, ctrl);
                },
                $setDirty: function () {
                    elm.removeClass('ng-pristine').addClass('ng-dirty');
                    ctrl.$dirty = true;
                    ctrl.$pristine = false;
                },
            };
            angular.extend(ctrl, isolatedFormCtrl);
        }
    };
}]);

要使用它,只需调用指令"隔离形式":

<form name="parent">
    <input type="text" ng-model="outside"/>
    <ng-form name="subform" isolate-form>
        <input type="text" ng-model="inside"/>
    </ng-form>
</form>

我遇到了同样的问题。在一个更大的窗体中,我需要有一个包含多个控件的子窗体,这些控件不应触及父窗体的状态。

这是我的解决方案:我写了一个指令"null-form",它从父表单中删除子表单,并且不发送任何状态更改其父表单。

angular.module('nullForm',[]).directive('nullForm', [function () {
  return {
    restrict: 'A',
    require: '?form',
    link: function link(scope, element, iAttrs, formController) {
      if (! formController) {
        return;
      }
      // Remove this form from parent controller
      var parentFormController = element.parent().controller('form');
      parentFormController.$removeControl(formController);
      // Replace form controller with a "null-controller"
      var nullFormCtrl = {
        $addControl: angular.noop,
        $removeControl: angular.noop,
        $setValidity: angular.noop,
        $setDirty: angular.noop,
        $setPristine: angular.noop
      };
      angular.extend(formController, nullFormCtrl);
    }
  };
}]);

然后,您可以像这样使用它:

<form name="parent">
  <input type="text" ng-model="outside"/>
  <ng-form name="subform" null-form>
    <input type="text" ng-model="inside"/>
  </ng-form>
</form>
对"

内部"的任何更改或否定验证都不会对"父级"产生影响。

然而,由于这个解决方案,有一个缺点:子表单也不会有任何状态,它的CSS类(如ng-validing等)也不会工作。为此,您需要从原始表单控制器重新实现此功能。

至少在 Angular 1.5 中,使用 $removeControl 从父级中删除嵌套表单似乎就足够了:

module.directive('isolateForm', function() {
  return {
    restrict: 'A',
    require: '?form',
    link: function(scope, element, attrs, formController) {
      if (!formController) {
        return;
      }
      var parentForm = formController.$$parentForm; // Note this uses private API
      if (!parentForm) {
        return;
      }
      // Remove this form from parent controller
      parentForm.$removeControl(formController);
    }
  };
});

瞧,父级的原始状态和有效性状态不再受嵌套形式的影响。

我发现最有效的解决方案是安东的解决方案。

设置 mbernath 建议的 nullFormCtrl 会禁用对子窗体的验证(不过,这要为铺平道路......

我所做的唯一更改是访问父窗体的方式。 Angular 确实为此提供了一种方法。

.directive('isolateForm', [function () {
    return {
        restrict: 'A',
        require: '?form',
        link: function link(scope, element, iAttrs, formController) {
            if (!formController) {
                return;
            }
            // Remove this form from parent controller
            formController.$$parentForm.$removeControl(formController)
            var _handler = formController.$setValidity;
            formController.$setValidity = function (validationErrorKey, isValid, cntrl) {
                _handler(validationErrorKey, isValid, cntrl);
                formController.$$parentForm.$setValidity(validationErrorKey, true, this);
            }
        }
    };
}]);

在角度形式可以嵌套。这意味着当所有子窗体也有效时,外部窗体也有效。

因此,当内部形式之一无效时,没有办法使外部形式自动有效(通过$valid键)。

尝试使用error.required

   <h3>Outer form (valid={{!fOuter.txtOuter.$error.required}})</h3>

演示小提琴

来自 Angular ngForm 文档:

另一种方式应该是使用控制器,例如:

<h3>Outer form (valid={{isOuterFormValid}})</h3>

控制器

$scope.isOuterFormValid = true;
// here, add listener on each input and change flag `isOuterFormValid`
... 

我是 Angular 的新手,但是请检查以下方法是否有帮助。

<div ng-app ng-controller="Ctrl"> <ng-form name="fOuter"> <h3>Outer form (valid={{fOuter.$valid}})</h3> <ng-form name="fInner1"> <h3>Inner form 1 (valid={{fInner1.$valid}})</h3> <input type="text" name="txtInner1" ng-model="outer" placeholder="(required)" required /> </ng-form> <ng-form name="fInner2"> <h3>Inner form 2 (valid={{fInner2.$valid}})</h3> <input type="text" name="txtInner2" ng-model="inner" placeholder="(required)" required /> </ng-form> </ng-form> </div>

我遇到了同样的问题,并通过对 angular.js 文件本身的本地副本进行位更改来解决它。

基本上,我向表单控制器添加了新功能,如下所示:

form.$resetParent = function() {
    parentForm = nullFormCtrl;
};

并创建自定义指令:

angular.module('myApp').directive('dtIsolatedForm', function () {
    return {
        restrict: 'A',
        require: '?form',
        link: function (scope, element, attrs, formController) {
            if (!formController || !formController.$parentForm) {
                return;
            }
            formController.$resetParent();
        }
    };
});

同样受到mbernath的启发,我找到了一个更简单的解决方案。它包括创建一个仅用于隔离的虚拟表单类指令。该指令停止从嵌套元素到外部表单的传播,但它没有任何表单功能。您可以将ngForms嵌套在内部并使它们完全正常运行。

angular.directive('formIsolator', function () {
            return {
                name: 'form',
                restrict: 'EAC',
                controller: function() {
                    this.$addControl = angular.noop;
                    this.$$renameControl = function(control, name) {
                        control.$name = name;
                    };
                    this.$removeControl = angular.noop;
                    this.$setValidity = angular.noop;
                    this.$setDirty = angular.noop;
                    this.$setPristine = angular.noop;
                    this.$setSubmitted = angular.noop;
                }
            };
        })

方法是在指令定义(name: 'form')中指定控制器的名称。此属性未记录,但用于在角度源中创建 ngForm 指令。

我想建议Mbernath的版本没有缺点

angular.module('yourModule').directive('isolatedForm', [function () {
return {
    restrict: 'A',
    require: '?form',
    link: function link(scope, element, iAttrs, formController) {
        if (!formController) return;
        // Remove this form from parent controller
        var parentFormController = element.parent().controller('form');
        parentFormController.$removeControl(formController);
        // override default behavior
        var _handler = formController.$setValidity;
        formController.$setValidity = function (validationErrorKey, isValid, cntrl) {
            _handler(validationErrorKey, isValid, cntrl);
            parentFormController.$setValidity(validationErrorKey, true, this);
        }
    }
};}]);

从控制器:

Ctrl.isOuterFormValid = function() {
    var outerFormIsValid = true;
    for(var prop in Ctrl.formName) {
        //The form is only inValid if the property is not a new form and it is invalid
        if(pvCtrl.pvForm[prop].constructor.name !== "FormController" &&
           pvCtrl.pvForm[prop].$invalid){
            outerFormIsValid = false;
        }
    }
    alert(outerFormIsValid);
};

FormController 是一个对象,它为您提供有关表单状态的信息。
使用ng-form将窗体添加到窗体就是将FormController属性添加到原始FormController对象。

这样做的好处是不会向所有输入元素添加 html 指令。

基本上,目标是分离嵌套表单之间的连接,并独立执行自己的验证/访问表单的$error对象。这可以通过在两个嵌套表单之间引入模型控制器并允许该模型控制器确定父表单控制器和子表单控制器何时应有效/无效来实现。这可以通过增加$setValidity()来实现,它决定了表单何时应该有效/无效。

请在下面的 plunker 链接中找到我的代码。我在父窗体和子窗体之间引入了一个模型控制器。在这里,我抽象了$error父窗体中的子窗体的对象。这意味着,父表单将无法看到子表单的问题,但是当子表单中的某些字段无效时,它将失效。只有中间模型控制器知道子窗体中哪些字段有问题。可以调整此逻辑或根据我们的需求进行扩展。如果有人需要更多代码方面的澄清,请告诉我。

[plnkr]: https://plnkr.co/edit/5gvctSSqmWiEAUE3YUcZ?p=preview

在检查表单是否有效之前,只需删除嵌套表单即可! vm.parentForm.$removeControl(vm.nestledForm);