调和Angular.js和Bootstrap表单验证样式


72

我在Bootstrap中使用Angular。这是参考代码:

<form name="newUserForm" ng-submit="add()" class="" novalidate>
    <input type="text" class="input" ng-model="newUser.uname" placeholder="Twitter" ng-pattern="/^@[A-Za-z0-9_]{1,15}$/" required></td>
    <button type="submit" ng-disabled="newUserForm.$invalid" class="btn btn-add btn-primary">Add</button>
</form>

Bootstrap具有以下形式的无效字段样式input:invalid {.... }:这些字段在字段为空时开始。现在我也通过Angular进行了一些模式匹配。当“:invalid”关闭但“ .ng-invalid”打开时,这会产生奇怪的情况,这将需要我为“ .ng-invalid”类重新实现引导CSS类。

我看到两个选择,但都遇到麻烦

  • 使Angular使用一些自定义类名而不是“ ng-valid”(我不知道该怎么做)。
  • 禁用html5验证(我认为那是form标记中的“ novalidate”属性应该执行的操作,但由于某种原因无法使它起作用)。

此处的Angular-Bootstrap指令不涵盖样式。


3
novalidate应该“禁用浏览器的本机表单验证” –表单文档
Mark Rajcok

Answers:


92

使用Bootstrap的“错误”类进行样式设置。您可以编写更少的代码。

<form name="myForm">
  <div class="control-group" ng-class="{error: myForm.name.$invalid}">
    <label>Name</label>
    <input type="text" name="name" ng-model="project.name" required>
    <span ng-show="myForm.name.$error.required" class="help-inline">
        Required</span>
  </div>
</form>

编辑: 正如其他答案和注释所指出的那样-在Bootstrap 3中,该类现在是“有错误”,而不是“错误”。


53
或者,如果您使用的是bootstrap 3ng-class="{'has-error': myForm.name.$invalid}"
bibstha,2013年

17
您还可以添加&& myForm.name.$dirty使验证样式仅在用户与表单控件进行交互之后才显示。
mWillis 2014年

@bibstha的bs3帮助内联怎么办?
SuperUberDuper 2014年

5
这行得通,但是这是一个巨大的开销,并且使模板变得非常冗长。我正在寻找一种更整洁的方式。
BenCr 2014年

向下滚动几个答案后,我立即发现了这一点。
BenCr 2014年

47

Bootstrap 3中的类已更改:

<form class="form-horizontal" name="form" novalidate ng-submit="submit()" action="/login" method="post">
  <div class="row" ng-class="{'has-error': form.email.$invalid, 'has-success': !form.email.$invalid}">
    <label for="email" class="control-label">email:</label>
    <div class="col">
    <input type="email" id="email" placeholder="email" name="email" ng-model="email" required>
    <p class="help-block error" ng-show="form.email.$dirty && form.email.$error.required">please enter your email</p>
    <p class="help-block error" ng-show="form.email.$error.email">please enter a valid email</p>
  ...

请注意和周围的引号'has-error''has-success'花了一段时间才发现...


有没有人ng-class="(form.email.$invalid ? 'has-error' : 'has-success')"上班?
kristianlm

4
为了避免在页面加载后输入立即显示为无效,我认为您应该检查$ dirty属性,指示该字段是否已被编辑:{'has-error': form.email.$dirty && form.email.$invalid, 'has-success': form.email.$dirty && !form.email.$invalid}但是现在此表达式变得很长,以至于容易产生键入错误并且难以阅读,并且它始终类似,所以应该有更好的方法,不是吗?
mono68 2013年

1
我用一个指令,增加了一个“提交”标志的形式,即见:stackoverflow.com/questions/14965968/...
malix

1
@kristianlm您是否在div上的div上输入了tryng-class =“ {'has-error':form.email。$ invalid,'has-success':!form.email。$ invalid}” ...
malix

@malix可能有效,但是我希望不必重复form.email。$ invalid。
kristianlm

34

另一个解决方案:创建指令,has-error根据子输入切换类。

app.directive('bsHasError', [function() {
  return {
      restrict: "A",
      link: function(scope, element, attrs, ctrl) {
          var input = element.find('input[ng-model]'); 
          if (input.length) {
              scope.$watch(function() {
                  return input.hasClass('ng-invalid');
              }, function(isInvalid) {
                  element.toggleClass('has-error', isInvalid);
              });
          }
      }
  };
}]);

然后简单地在模板中使用它

<div class="form-group" bs-has-error>
    <input class="form-control" ng-model="foo" ng-pattern="/.../"/>
</div>

3
我认为在这种情况下,指令是最好的解决方案。
JKillian 2014年

2
值得注意的是,jqlite不支持element[attribute]选择器语法,因此如果您不使用jQuery,则需要进行一些修改。
塔林2015年

1
如果不提供表述,手表将永远不会被丢弃
Anders

3
与其查看类,不如直接查看NgModelController属性会更好吗?input.controller('ngModel').$invalid代替input.hasClass('ng-invalid')。在$ watch触发之前,css类没有更新的问题。
Thomas Wajs 2015年

@Thomas Wajs是绝对正确的。上面的解决方案(以及此处发布的其他解决方案)将始终是一个摘要循环不同步,因为评估是在摘要类别的中间进行的,然后才更新了无效的类。input.controller('ngModel')。$ invalid可解决此问题。
罗恩

22

@farincz的答案的细微改进。我同意指令是此处的最佳方法,但我不想在每个.form-group元素上都重复它,因此我更新了代码以允许将其添加到.form-group或父<form>元素中(它将添加到所有包含的.form-group元素中) ):

angular.module('directives', [])
  .directive('showValidation', [function() {
    return {
        restrict: "A",
        link: function(scope, element, attrs, ctrl) {

            if (element.get(0).nodeName.toLowerCase() === 'form') {
                element.find('.form-group').each(function(i, formGroup) {
                    showValidation(angular.element(formGroup));
                });
            } else {
                showValidation(element);
            }

            function showValidation(formGroupEl) {
                var input = formGroupEl.find('input[ng-model],textarea[ng-model]');
                if (input.length > 0) {
                    scope.$watch(function() {
                        return input.hasClass('ng-invalid');
                    }, function(isInvalid) {
                        formGroupEl.toggleClass('has-error', isInvalid);
                    });
                }
            }
        }
    };
}]);

17

对@Andrew Smith的答案的细微改进。我更改输入元素并使用require关键字。

.directive('showValidation', [function() {
    return {
        restrict: "A",
        require:'form',
        link: function(scope, element, attrs, formCtrl) {
            element.find('.form-group').each(function() {
                var $formGroup=$(this);
                var $inputs = $formGroup.find('input[ng-model],textarea[ng-model],select[ng-model]');

                if ($inputs.length > 0) {
                    $inputs.each(function() {
                        var $input=$(this);
                        scope.$watch(function() {
                            return $input.hasClass('ng-invalid');
                        }, function(isInvalid) {
                            $formGroup.toggleClass('has-error', isInvalid);
                        });
                    });
                }
            });
        }
    };
}]);

需要更改为$(element)。jqlite不支持按类别搜索。(我尝试提交编辑,但我需要更改6个字符。...)
本·乔治

11

谢谢@farincz的出色回答。这是我为适应用例所做的一些修改。

此版本提供了三个指令:

  • bs-has-success
  • bs-has-error
  • bs-has (当您要同时使用其他两个时的便利)

我所做的修改:

  • 添加了一个检查,以仅在表单字段为脏状态时显示具有状态,即只有在有人与他们互动时才会显示它们。
  • element.find()为不使用jQuery的用户更改了传递给它的字符串,因为 element.find()Angular的jQLite仅支持按标记名查找元素。
  • 添加了对选择框和文本区域的支持。
  • 包装在element.find()$timeout以支持元素可能尚未将其子元素呈现给DOM的情况(例如,如果元素的子元素标记为ng-if)。
  • 改变if的表达来检查返回数组的长度(if(input)@ farincz的回答总是返回true,因为从回报element.find()是一个jQuery数组)。

我希望有人觉得这有用!

angular.module('bs-has', [])
  .factory('bsProcessValidator', function($timeout) {
    return function(scope, element, ngClass, bsClass) {
      $timeout(function() {
        var input = element.find('input');
        if(!input.length) { input = element.find('select'); }
        if(!input.length) { input = element.find('textarea'); }
        if (input.length) {
            scope.$watch(function() {
                return input.hasClass(ngClass) && input.hasClass('ng-dirty');
            }, function(isValid) {
                element.toggleClass(bsClass, isValid);
            });
        }
      });
    };
  })
  .directive('bsHasSuccess', function(bsProcessValidator) {
    return {
      restrict: 'A',
      link: function(scope, element) {
        bsProcessValidator(scope, element, 'ng-valid', 'has-success');
      }
    };
  })
  .directive('bsHasError', function(bsProcessValidator) {
    return {
      restrict: 'A',
      link: function(scope, element) {
        bsProcessValidator(scope, element, 'ng-invalid', 'has-error');
      }
    };
  })
  .directive('bsHas', function(bsProcessValidator) {
    return {
      restrict: 'A',
      link: function(scope, element) {
        bsProcessValidator(scope, element, 'ng-valid', 'has-success');
        bsProcessValidator(scope, element, 'ng-invalid', 'has-error');
      }
    };
  });

用法:

<!-- Will show success and error states when form field is dirty -->
<div class="form-control" bs-has>
  <label for="text"></label>
  <input 
   type="text" 
   id="text" 
   name="text" 
   ng-model="data.text" 
   required>
</div>

<!-- Will show success state when select box is anything but the first (placeholder) option -->
<div class="form-control" bs-has-success>
  <label for="select"></label>
  <select 
   id="select" 
   name="select" 
   ng-model="data.select" 
   ng-options="option.name for option in data.selectOptions"
   required>
    <option value="">-- Make a Choice --</option>
  </select>
</div>

<!-- Will show error state when textarea is dirty and empty -->
<div class="form-control" bs-has-error>
  <label for="textarea"></label>
  <textarea 
   id="textarea" 
   name="textarea" 
   ng-model="data.textarea" 
   required></textarea>
</div>

您也可以安装Guilherme的bower软件包,将所有这些软件包捆绑在一起。


1
我已经在凉亭上将其发布为“ angular-bootstrap-validation”,感谢您和@farincz,希望您不要介意
Gui

2
这很酷。我注意到您已经添加了一个功能,可以将该指令放在表单级别,并使其通过嵌套.form-group元素递归。很好,但是除非您包含jQuery,否则它将不起作用,因为内置的angular jqlite实现find仅支持按标记名而不是选择器进行查找。为此,您可能需要在自述文件中添加一个注释。
Tom Spencer

您可以form.$submitted用来仅显示提交时的错误。此处的Angular文档中有一个示例:docs.angularjs.org/guide/forms。查找标题“绑定到表单和控件状态”。
杰夫·基尔布莱德

4

如果样式是问题,但您不想禁用本机验证,那么为什么不使用自己的样式来覆盖样式, 更具体的样式样式呢?

input.ng-invalid, input.ng-invalid:invalid {
   background: red;
   /*override any styling giving you fits here*/
}

用CSS选择器特异性解决您的问题!


2

我对Jason Im的回答的改进如下:添加了两个新指令show-validation-errors和show-validation-error。

'use strict';
(function() {

    function getParentFormName(element,$log) {
        var parentForm = element.parents('form:first');
        var parentFormName = parentForm.attr('name');

        if(!parentFormName){
            $log.error("Form name not specified!");
            return;
        }

        return parentFormName;
    }

    angular.module('directives').directive('showValidation', function () {
        return {
            restrict: 'A',
            require: 'form',
            link: function ($scope, element) {
                element.find('.form-group').each(function () {
                    var formGroup = $(this);
                    var inputs = formGroup.find('input[ng-model],textarea[ng-model],select[ng-model]');

                    if (inputs.length > 0) {
                        inputs.each(function () {
                            var input = $(this);
                            $scope.$watch(function () {
                                return input.hasClass('ng-invalid') && !input.hasClass('ng-pristine');
                            }, function (isInvalid) {
                                formGroup.toggleClass('has-error', isInvalid);
                            });
                            $scope.$watch(function () {
                                return input.hasClass('ng-valid') && !input.hasClass('ng-pristine');
                            }, function (isInvalid) {
                                formGroup.toggleClass('has-success', isInvalid);
                            });
                        });
                    }
                });
            }
        };
    });

    angular.module('directives').directive('showValidationErrors', function ($log) {
        return {
            restrict: 'A',
            link: function ($scope, element, attrs) {
                var parentFormName = getParentFormName(element,$log);
                var inputName = attrs['showValidationErrors'];
                element.addClass('ng-hide');

                if(!inputName){
                    $log.error("input name not specified!")
                    return;
                }

                $scope.$watch(function () {
                    return !($scope[parentFormName][inputName].$dirty && $scope[parentFormName][inputName].$invalid);
                },function(noErrors){
                    element.toggleClass('ng-hide',noErrors);
                });

            }
        };
    });

    angular.module('friport').directive('showValidationError', function ($log) {
        return {
            restrict: 'A',
            link: function ($scope, element, attrs) {
                var parentFormName = getParentFormName(element,$log);
                var parentContainer = element.parents('*[show-validation-errors]:first');
                var inputName = parentContainer.attr('show-validation-errors');
                var type = attrs['showValidationError'];

                element.addClass('ng-hide');

                if(!inputName){
                    $log.error("Could not find parent show-validation-errors!");
                    return;
                }

                if(!type){
                    $log.error("Could not find validation error type!");
                    return;
                }

                $scope.$watch(function () {
                    return !$scope[parentFormName][inputName].$error[type];
                },function(noErrors){
                    element.toggleClass('ng-hide',noErrors);
                });

            }
        };
    });

})();

可以将show-validation-errors添加到错误容器中,以便它将根据表单字段的有效性显示/隐藏该容器。

并且show-validation-error根据给定类型上的表单字段有效性显示或隐藏元素。

预期用途的示例:

        <form role="form" name="organizationForm" novalidate show-validation>
            <div class="form-group">
                <label for="organizationNumber">Organization number</label>
                <input type="text" class="form-control" id="organizationNumber" name="organizationNumber" required ng-pattern="/^[0-9]{3}[ ]?[0-9]{3}[ ]?[0-9]{3}$/" ng-model="organizationNumber">
                <div class="help-block with-errors" show-validation-errors="organizationNumber">
                    <div show-validation-error="required">
                        Organization number is required.
                    </div>
                    <div show-validation-error="pattern">
                        Organization number needs to have the following format "000 000 000" or "000000000".
                    </div>
                </div>
            </div>
       </form>

对于验证错误,您还可以使用ngMessage
Sander Rijken 2015年

2

我认为现在答复已经太迟了,但希望您会喜欢它:

CSS,您可以添加其他类型的控件,例如选择,日期,密码等

input[type="text"].ng-invalid{
    border-left: 5px solid #ff0000;
    background-color: #FFEBD6;
}
input[type="text"].ng-valid{
    background-color: #FFFFFF;
    border-left: 5px solid #088b0b;
}
input[type="text"]:disabled.ng-valid{
    background-color: #efefef;
    border: 1px solid #bbb;
}

HTML:除了ng-required之外,无需在控件中添加任何内容

<input type="text"
       class="form-control"
       ng-model="customer.ZipCode"
       ng-required="true">

只需尝试一下,然后在控件中键入一些文本,我就会发现它非常方便且令人敬畏。


1

很难说清楚,但是在查看angular.js代码时,它不会替换类-它只是添加和删除自己的类。因此,任何引导类(由引导UI脚本动态添加)都应保持角度不变。

也就是说,与Angular同时使用Bootstrap的JS功能进行验证是没有意义的-仅使用Angular。我建议您使用引导样式和有角度的JS,即使用自定义验证指令将引导css类添加到元素中。


没错,禁用本机验证是必经之路。但是我还没有做到这一点。我会继续寻找。谢谢!
Ivan P

1
<div class="form-group has-feedback" ng-class="{ 'has-error': form.uemail.$invalid && form.uemail.$dirty }">
  <label class="control-label col-sm-2" for="email">Email</label>
  <div class="col-sm-10">
    <input type="email" class="form-control" ng-model="user.email" name="uemail" placeholder="Enter email" required>
    <div ng-show="form.$submitted || form.uphone.$touched" ng-class="{ 'has-success': form.uemail.$valid && form.uemail.$dirty }">
    <span ng-show="form.uemail.$valid" class="glyphicon glyphicon-ok-sign form-control-feedback" aria-hidden="true"></span>
    <span ng-show="form.uemail.$invalid && form.uemail.$dirty" class="glyphicon glyphicon-remove-circle form-control-feedback" aria-hidden="true"></span>
    </div>
  </div>
</div>

1

当我没有听说过AngularJS本身的名称时,我知道这是一个非常古老的问题解答线程:-)

但是对于那些登陆该页面以干净,自动化的方式寻找Angular + Bootstrap表单验证的人来说,我编写了一个很小的模块来实现相同的目的,而无需更改任何形式的HTML或Javascript。

Checkout Bootstrap角度验证

以下是三个简单步骤:

  1. 通过Bower安装 bower install bootstrap-angular-validation --save
  2. 添加脚本文件 <script src="bower_components/bootstrap-angular-validation/dist/bootstrap-angular-validation.min.js"></script>
  3. 将依赖项添加bootstrap.angular.validation到您的应用程序中就可以了!!

这适用于引导3和jQuery的不是必需的

这基于jQuery验证的概念。该模块提供了一些附加的验证和验证错误的通用通用消息。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.