Согласование стилей проверки формы Angular.js и Bootstrap

69

Я использую 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>

Bootstrap имеет стили для недопустимых полей в форме input:invalid {.... }; они пинают, когда поле пусто. Теперь у меня также есть сопоставление с образцом через Angular. Это создает нечетные случаи, когда ": invalid" выключен, но включен ".ng-invalid", что потребовало бы, чтобы я повторно реализовал классы CSS bootstrap для класса ".n-invalid".

Я вижу два варианта, но проблема с обоими

  • Сделать Angular использовать какое-то пользовательское имя класса вместо "ng-valid" (я не знаю, как это сделать).
  • Отключить проверку html5 (я подумал, что этот атрибут "novalidate" в теге формы должен делать, но не мог заставить его работать по какой-то причине).

Директивы Angular -Bootstrap там не охватывают стиль.

  • 3
    novalidate должен «отключить проверку собственной формы браузера» - форма документации

12 ответов

96
Лучший ответ

Использовать Bootstrap "error" для стилизации. Вы можете написать меньше кода.

<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>

EDIT: Как указывают другие ответы и комментарии - в Bootstrap 3 класс теперь имеет "has-error", а не "error".

  • 53
    Или, если вы используете bootstrap 3 ng-class="{'has-error': myForm.name.$invalid}"
  • 17
    Вы также можете добавить && myForm.name.$dirty чтобы стиль проверки отображался только после взаимодействия пользователя с && myForm.name.$dirty управления формы.
Показать ещё 4 комментария
45

Классы изменились в 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': потребовалось некоторое время, чтобы найти, что...

  • 0
    Кто-нибудь ng-class="(form.email.$invalid ? 'has-error' : 'has-success')" Has ng-class="(form.email.$invalid ? 'has-error' : 'has-success')" ?
  • 4
    Чтобы избежать появления некорректных входных данных сразу после загрузки страницы, я думаю, вам следует проверить свойство $ dirty, указывающее, было ли поле уже отредактировано: {'has-error': form.email.$dirty && form.email.$invalid, 'has-success': form.email.$dirty && !form.email.$invalid} Но теперь это выражение становится настолько длинным, что становится склонным к ошибкам ввода и становится трудночитаемым, и оно всегда похоже, поэтому должен быть лучший способ не?
Показать ещё 4 комментария
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
    Я думаю, что директива - лучшее решение в этом случае.
  • 2
    Стоит отметить, что jqlite не поддерживает синтаксис селектора element[attribute] , поэтому его нужно немного изменить, если вы не используете jQuery.
Показать ещё 3 комментария
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);
                        });
                    });
                }
            });
        }
    };
}]);
  • 0
    Необходимо изменить на $ (элемент). jqlite не поддерживает поиск по классу. (Я попытался внести изменения, но мне нужно изменить 6 символов ....)
10

Спасибо @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) from @farincz answer всегда возвращает 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>

Вы также можете установить Guillherme пакет bower, который объединяет все это вместе.

  • 1
    Я опубликовал его на bower как «angular-bootstrap-validation» с благодарностями вам и @farincz, надеюсь, вы не возражаете
  • 2
    Это круто. Я заметил, что вы добавили функцию, в которой вы можете поместить директиву на уровне формы и сделать так, чтобы она повторялась через вложенные .form-group . Это хорошо, но это не сработает, если вы не включите jQuery, так как встроенная угловая реализация jqlite поддерживает find только по тэгу, а не по селектору. Вы можете добавить примечание в README на этот счет.
Показать ещё 2 комментария
4

Если проблема с дизайном, но вы не хотите отключать встроенную проверку, почему бы не переопределить стиль с вашим собственным, более конкретным стилем?

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

Каскадируйте свои проблемы с помощью специфики селектора CSS!

2

Я думаю, что слишком поздно ответить, но надеюсь, что вам понравится:

CSS вы можете добавить другие типы элементов управления, такие как select, date, password и т.д.

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">

Просто попробуйте и введите текст в свой контроль, я считаю, что он действительно удобен и увлекателен.

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 могут быть добавлены в контейнер с ошибками, чтобы он отображал/скрывал контейнер на основе достоверности полей формы.

а ошибка 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>
  • 0
    Для ошибок проверки вы также можете использовать ngMessage
1

Я знаю, что это очень старый вопрос, когда я не слышал имя самого AngularJS: -)

Но для других, которые приземляются на эту страницу и ищут Angular + валидацию формы Bootstrap чистым и автоматическим способом, я написал довольно маленький модуль для достижения того же, не изменяя HTML или Javascript в любой форме.

Оформить заказ Bootstrap Angular Проверка.

Ниже перечислены три простых шага:

  • Установить через Bower bower install bootstrap-angular-validation --save
  • Добавьте script файл <script src="bower_components/bootstrap-angular-validation/dist/bootstrap-angular-validation.min.js"></script>
  • Добавьте зависимость bootstrap.angular.validation к вашему приложению и , что она!

Это работает с Bootstrap 3 и jQuery не требуется.

Это основано на концепции проверки jQuery. Этот модуль предоставляет некоторые дополнительные валидации и общие общие сообщения для ошибки проверки.

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

Трудно сказать наверняка без скрипки, но глядя на код angular.js, он не заменяет классы - он просто добавляет и удаляет свои собственные. Таким образом, любые классы bootstrap (добавленные динамически с помощью сценариев начальной загрузки) должны быть не затронуты angular.

Тем не менее, не имеет смысла использовать функцию Bootstrap JS для проверки в то же время, что и Angular - использовать только angular. Я бы посоветовал использовать стили бутстрапа, а Angular JS i.e добавить классы bootstrap css к вашим элементам, используя специальную директиву проверки.

  • 0
    Вы правы, отключение нативной проверки - это путь. И все же я не смог этого сделать. Я буду продолжать искать. Спасибо!

Ещё вопросы

Сообщество Overcoder
Наверх
Меню