使用AngularJS,如何一次将所有表单字段设置为$dirty?

使用AngularJS,如何一次将所有表单字段设置为$dirty?,angularjs,Angularjs,我使用AngularJS创建了一个HTML表单,并在一些字段中添加了required属性 对于这些字段,如果字段不是$pristine并且$invalid,我会显示一条错误消息: <input type="text" ng-model="SomeModel.SomeProperty" name="myField" class="input-block-level" required> <p ng-show="frmMyForm.myField.$invalid &&am

我使用AngularJS创建了一个HTML表单,并在一些字段中添加了
required
属性

对于这些字段,如果字段不是
$pristine
并且
$invalid
,我会显示一条错误消息:

<input type="text" ng-model="SomeModel.SomeProperty" name="myField" class="input-block-level" required>
<p ng-show="frmMyForm.myField.$invalid && !frmMyForm.myField.$pristine" class="error-text">This field is required!</p>

此字段是必需的

这个很好用。但是,如果用户只是跳过所需的字段(从不将光标放在其中),则该字段始终是原始字段,因此即使单击“提交”按钮,也不会显示错误消息。因此,用户面临一个表单,他们无法提交,但没有错误文本告诉他们为什么

我的想法是,在提交操作中将所有表单字段设置为
$dirty
,将触发错误消息,以显示用户跳过的任何必填字段。这可能吗?如果是,怎么做


提前谢谢。

我在发布这篇文章后不久就想出了一个答案。我不确定这是不是正确的方法,但它确实有效

在控制器中,只需添加“ShowValidationMessages”或类似内容的属性,并将其设置为
false

$scope.MyObject = {
   ShowValidationMessages: false
};
现在在字段级验证逻辑中引用此值:

<p ng-show="frmMyForm.myField.$invalid && (!frmMyForm.myField.$pristine || MyObject.ShowValidationMessages)" class="error-text">This field is required!</p>

我们做了一些与您的答案类似的事情,我们有一个绑定到submit事件的FormSubmited指令,如果触发,我们在表单控制器上设置$Submited变量。这样,您可以以与ShowValidationMessages类似的方式使用它,但它是可重用的。非常简单的指令:

app.directive('formSubmitted', [function () {
    return {
        restrict: 'A',
        require: 'form',
        link: function (scope, element, attrs, ctrl) {
            ctrl.$submitted = false;
            element.on('submit', function () {
                scope.$apply(function () {
                    ctrl.$submitted = true;
                });
            });
        }
    };
}]);
将其作为属性应用于表单标记本身

我们进一步做了几步,我们的要求是只有在以下情况成立时才显示验证错误:元素无效,表单已提交或输入元素已模糊。因此,我们最终得到了另一个指令,该指令要求ngModel在ngModel控制器上设置元素的模糊状态

最后,为了消除html中大量重复的锅炉板代码来检查所有这些内容,例如,您的
ng show=“frmMyForm.myField.$invalid&&(!frmMyForm.myField.$pristine | | MyObject.ShowValidationMessages)”
我们也将其封装到一个指令中。这个模板指令用引导模板包装我们的输入元素,并处理所有的验证工作。现在我所有的表单输入都遵循这个模式:

<div data-bc-form-group data-label="Username:">
    <input type="text" id="username" name="username" ng-model="vm.username" data-bc-focus required />
</div>

我做了一些简单的事情

 $scope.FormName.InputName.$pristine = false;
if($scope.FormName.$invalid)
return;

表单失效时,包含
$error
属性

$error
是一个对象哈希,包含对验证程序失败的控件或窗体的引用

因此,您可以简单地循环遍历error对象,并在每个控件上使用
$setDirty()

// "If the name attribute is specified, the form controller is published onto the current scope under this name."
var form = scope.myForm;

if (form.$invalid) {
    angular.forEach(form.$error, function(controls, errorName) {
        angular.forEach(controls, function(control) {
            control.$setDirty();
        });
    });
}

为什么不在按钮上使用ng disabled=“frmMyForm.$invalid”?这样,只要表单无效,您的按钮就会被禁用。@snaplemouton--谢谢,我一开始就是这样做的,但这种方法的问题是相同的:按钮被禁用,用户可能不知道为什么,因为验证没有触发(显示错误文本)。非常非常好。谢谢这正是我所需要的,而且可能是使用AngularJS进行表单验证的最佳实践。我对bcFormGroup指令的实现有点纠结:你手头有一些代码吗?@CornelMasson请参阅更新的答案,它列出了基础知识,应该给你一个很好的起点。@Vincent这个答案不依赖于Angular的任何版本,它在1.2上可以正常工作。唯一的区别是,在1.2中,我们在表单控制器上创建了$submitted,而在1.3和1.3中,变量已经存在,所以我们只需设置值。@xtof$HASMULED是由另一个客户指令设置的,请参见更新的答案。实际上,我非常喜欢这个解决方案。它相当简洁,不需要定义自定义指令,同时还避免了在控制器中按名称引用任何控件。(顺便说一句,您不必使用angular.forEach来实现此功能-您可以根据需要迭代控件-但这只是一个偏好问题。)作为带胖箭头函数的一行程序:
angular.forEach(form.$error,controls=>controls.forEach(control=>control.$setDirty())我认为这很好,但我建议不要这样做,因为它需要在控制器中按名称引用表单控件,这通常是要避免的,如果有很多控件,它也会变成很多代码。我建议@nick的答案是一个基于类似想法的更干净的解决方案。
app.directive('bcFormGroup', ['$compile', '$interpolate', function ($compile, $interpolate) {
  return {
    restrict: 'A',
    template:
        '<div class="form-group" ng-class="{\'has-error\': showFormGroupError()}">' +
            '<label for="{{inputId}}" class="col-md-3 control-label">{{label}}</label>' +
            '<div class="col-md-9">' +
                '<bc-placeholder></bc-placeholder>' +
            '</div>' +
        '</div>',
    replace: true,
    transclude: true,
    require: '^form',
    scope: {
        label: '@',
        inputTag: '@'
    },

    link: function (scope, element, attrs, formController, transcludeFn) {

        transcludeFn(function (clone) {
            var placeholder = element.find('bc-placeholder');
            placeholder.replaceWith(clone);
        });

        var inputTagType = scope.inputTag || 'input';
        var inputElement = element.find(inputTagType);
        var fqFieldName = formController.$name + '.' + inputElement.attr('name');
        var formScope = inputElement.scope();

        if (inputElement.attr('type') !== 'checkbox' && inputElement.attr('type') !== 'file') {
            inputElement.addClass('form-control');
        }

        scope.inputId = $interpolate(inputElement.attr('id'))(formScope);
        scope.hasError = false;
        scope.submitted = false;

        formScope.$watch(fqFieldName + '.$invalid', function (hasError) {
            scope.hasError = hasError;
        });

        formScope.$watch(formController.$name + '.$submitted', function (submitted) {
            scope.submitted = submitted;
        });

        if (inputElement.attr('data-bc-focus') != null || inputElement.attr('bc-focus') != null) {
            scope.hasBlurred = false;
            formScope.$watch(fqFieldName + '.$hasBlurred', function (hasBlurred) {
                scope.hasBlurred = hasBlurred;
            });
        }

        if (inputElement.attr('required')) {
            scope.hasRequiredError = false;
            formScope.$watch(fqFieldName + '.$error.required', function (required) {
                scope.hasRequiredError = required;
            });
            inputElement.after($compile('<span class="help-block" ng-show="showRequiredError()">Required</span>')(scope));
        }

        if (inputElement.attr('type') === 'email') {
            scope.hasEmailError = false;
            formScope.$watch(fqFieldName + '.$error.email', function (emailError) {
                scope.hasEmailError = emailError;
            });
            inputElement.after($compile('<span class="help-block" ng-show="showEmailError()">Invalid email address</span>')(scope));
        }

        scope.showFormGroupError = function () {
            return scope.hasError && (scope.submitted || (scope.hasBlurred === true));
        };

        scope.showRequiredError = function () {
            return scope.hasRequiredError && (scope.submitted || (scope.hasBlurred === true));
        };

        scope.showEmailError = function () {
            return scope.hasEmailError && (scope.submitted || (scope.hasBlurred === true));
        };

    }
  };
}]);
app.directive('bcFocus', [function () {
    var focusClass = 'bc-focused';
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, element, attrs, ctrl) {
            ctrl.$focused = false;
            ctrl.$hasBlurred = false;
            element.on('focus', function () {
                element.addClass(focusClass);
                var phase = scope.$root.$$phase;
                if (phase == '$apply' || phase == '$digest') {
                    ctrl.$focused = true;
                } else {
                    scope.$apply(function () {
                        ctrl.$focused = true;
                    });
                }
            }).on('blur', function () {
                element.removeClass(focusClass);
                var phase = scope.$root.$$phase;
                if (phase == '$apply' || phase == '$digest') {
                    ctrl.$focused = false;
                    ctrl.$hasBlurred = true;
                } else {
                    scope.$apply(function () {
                        ctrl.$focused = false;
                        ctrl.$hasBlurred = true;
                    });
                }
            });
        }
    };
}]);
 $scope.FormName.InputName.$pristine = false;
if($scope.FormName.$invalid)
return;
// "If the name attribute is specified, the form controller is published onto the current scope under this name."
var form = scope.myForm;

if (form.$invalid) {
    angular.forEach(form.$error, function(controls, errorName) {
        angular.forEach(controls, function(control) {
            control.$setDirty();
        });
    });
}