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

Iva*_*n P 72 twitter-bootstrap angularjs

我正在使用Angular和Bootstrap.以下是供参考的代码:

<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>
Run Code Online (Sandbox Code Playgroud)

Bootstrap具有无效字段的样式,形式为input:invalid {.... }; 当田地空无一人时,这些就开始了.现在我也通过Angular进行了一些模式匹配.当":invalid"关闭但是".ng-invalid"打开时,这会产生奇怪的情况,这需要我为".ng-invalid"类重新实现bootstrap CSS类.

我看到两种选择,但两者都有问题

  • 让Angular使用一些自定义类名而不是"ng-valid"(我不知道如何做到这一点).
  • 禁用html5验证(我认为这是表单标签中的"novalidate"属性应该做的,但由于某些原因无法使其工作).

Angular-Bootstrap指令不包括样式.

小智 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>
Run Code Online (Sandbox Code Playgroud)

编辑: 正如其他答案和评论所指出的 - 在Bootstrap 3中,类现在是"has-error",而不是"error".

  • 或者如果你使用bootstrap 3`ng-class ="{'has-error':myForm.name.$ invalid}"` (53认同)
  • 您还可以添加`&& myForm.name.$ dirty`以使验证样式仅在用户与表单控件交互后显示. (17认同)
  • 这可行,但这是一个巨大的开销,使模板非常冗长.我正在寻找一种更整洁的方式. (5认同)

mal*_*lix 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>
  ...
Run Code Online (Sandbox Code Playgroud)

注意引号'has-error''has-success':一段时间以后才发现...

  • 为了避免输入在页面加载后出现无效,我认为你应该检查$ dirty属性,指示该字段是否已被编辑:`{'has-error':form.email.$ dirty && form.email.$ invalid, 'has-success':form.email.$ dirty &&!form.email.$ invalid}`但现在这个表达式变得如此之长,以至于它容易出现输入错误并且难以阅读而且它总是相似所以应该有一个更好的方式,不是吗? (4认同)

far*_*ncz 34

另一个解决方案:Create指令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);
              });
          }
      }
  };
}]);
Run Code Online (Sandbox Code Playgroud)

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

<div class="form-group" bs-has-error>
    <input class="form-control" ng-model="foo" ng-pattern="/.../"/>
</div>
Run Code Online (Sandbox Code Playgroud)

  • 我认为指令是这种情况下的最佳解决方案. (3认同)
  • 而不是查看类,直接查看NgModelController属性不是更好吗?`input.controller('ngModel').$ invalid`而不是`input.hasClass('ng-invalid')`.在$ watch触发器之前,我遇到了没有更新css类的问题. (3认同)
  • 值得注意的是jqlite不支持`element [attribute]`选择器语法,所以如果你不使用jQuery,这需要修改一下. (2认同)

eme*_*hie 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);
                    });
                }
            }
        }
    };
}]);
Run Code Online (Sandbox Code Playgroud)


小智 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);
                        });
                    });
                }
            });
        }
    };
}]);
Run Code Online (Sandbox Code Playgroud)


Tom*_*cer 11

感谢@farincz给出了一个很好的答案.以下是我为了适应我的用例而进行的一些修改.

此版本提供三个指令:

  • bs-has-success
  • bs-has-error
  • bs-has (当你想要另外两个一起使用的时候方便)

我做的修改:

  • 添加了一个检查,仅在表单字段为脏时显示has状态,即在有人与它们交互之前不会显示它们.
  • 改变了element.find()那些不使用jQuery 的字符串传入的字符串,因为 element.find()在Angular的jQLite中只支持按标记名查找元素.
  • 添加了对选择框和textareas的支持.
  • 包含element.find()在a中$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');
      }
    };
  });
Run Code Online (Sandbox Code Playgroud)

用法:

<!-- 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>
Run Code Online (Sandbox Code Playgroud)

您还可以安装Guilherme的凉亭包,将所有这些捆绑在一起.

  • 这很酷.我注意到你添加了一个功能,你可以将指令放在表单级别,并通过嵌套的`.form-group`元素进行递归.这很好,但除非你包含jQuery,否则它将无法工作,因为`find`的内置角度jqlite实现仅支持通过标记名查找,而不是通过选择器查找.您可能希望在自述文件中添加一个注释. (2认同)