web-dev-qa-db-fra.com

Réconcilier Angular.js et Bootstrap

J'utilise Angular avec Bootstrap. Voici le code de référence:

<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 a des styles pour les champs invalides sous la forme de input:invalid {.... }; ceux-ci entrent en jeu lorsque le champ est vide. Maintenant, j'ai aussi un motif correspondant par l'intermédiaire angulaire. Cela crée des cas étranges lorsque ": invalid" est désactivé, mais que ".ng-invalid" est activé, ce qui m'obligerait à réimplémenter bootstrap les classes CSS pour la classe ".ng-invalid".

Je vois deux options, mais avoir des problèmes avec les deux

  • Make Angular utilisez un nom de classe personnalisé au lieu de "ng-valid" (je ne sais pas comment faire cela).
  • Désactivez la validation html5 (je pensais que c'était ce que devrait faire l'attribut "novalidate" dans la balise de formulaire, mais je ne pouvais pas le faire fonctionner pour une raison quelconque).

Les directives Angular-Bootstrap ne couvrent pas le style.

72
Ivan P

Utilisez la classe "error" de Bootstrap pour le style. Vous pouvez écrire moins de code.

<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: Comme le soulignent d’autres réponses et commentaires - in Bootstrap 3, la classe est maintenant "has-error", pas "error".

92
whuhacker

Les classes ont changé dans 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>
  ...

Notez les guillemets autour de 'has-error' et 'has-success': a mis du temps à trouver ça ...

47
malix

Une autre solution: Créer une directive qui bascule has-error classe selon une entrée enfant.

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);
              });
          }
      }
  };
}]);

puis utilisez-le simplement dans un modèle

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

Amélioration mineure à réponse de @ farincz . Je conviens qu’une directive est la meilleure approche ici, mais je ne voulais pas la répéter à chaque .form-group element, j’ai donc mis à jour le code pour permettre de l’ajouter à la .form-group ou au parent <form> élément (qui l’ajoutera à tous les contenus .form-group éléments):

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);
                    });
                }
            }
        }
    };
}]);
22
emertechie

Amélioration mineure de la réponse de @Andrew Smith. Je change d'éléments d'entrée et j'utilise le mot clé 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);
                        });
                    });
                }
            });
        }
    };
}]);
17
Jason Im

Merci à @farincz pour cette excellente réponse. Voici quelques modifications que j'ai apportées pour correspondre à mon cas d'utilisation.

Cette version fournit trois directives:

  • bs-has-success
  • bs-has-error
  • bs-has (Pratique pour utiliser les deux autres ensemble)

Les modifications que j'ai apportées:

  • Ajout d’une coche pour n’afficher que les états a lorsque le champ de formulaire est sale, c’est-à-dire qu’ils ne seront pas affichés tant que personne n’aura pas interagi avec eux.
  • Modification de la chaîne passée dans element.find() pour ceux qui n'utilisent pas jQuery, car element.find() dans jularite d'Angular ne prend en charge que la recherche d'éléments par nom.
  • Ajout du support pour les zones de sélection et textareas.
  • Enveloppé la element.find() dans un $timeout Pour prendre en charge les cas où l'élément n'a pas encore ses enfants rendus au DOM (par exemple, si un enfant de l'élément est marqué avec ng-if ).
  • Modification de l'expression if pour vérifier la longueur du tableau renvoyé (if(input) de réponse de @ farincz renvoie toujours la valeur true, comme le retour de element.find() est un tableau jQuery).

J'espère que quelqu'un trouvera cela utile!

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');
      }
    };
  });

Usage:

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

Vous pouvez également installer le package bower de Guilherme qui regroupe tout cela ensemble.

11
Tom Spencer

Si le style pose problème, mais que vous ne souhaitez pas désactiver la validation native, pourquoi ne pas remplacer le style par votre propre style, plus spécifique style?

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

Cascade vos problèmes avec la spécificité de sélecteur CSS!

4
Ben Lesh

Mon amélioration à la réponse de Jason Im ajoute ce qui suit deux nouvelles directives show-validation-errors et 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);
                });

            }
        };
    });

})();

Les erreurs show-validation-peuvent être ajoutées à un conteneur d'erreurs afin qu'il puisse afficher/masquer le conteneur en fonction de la validité des champs du formulaire.

et l'erreur show-validation-error affiche ou masque un élément en fonction de la validité des champs de formulaire sur un type donné.

Un exemple d'utilisation prévue:

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

Je pense qu'il est trop tard pour répondre mais j'espère que vous allez l'aimer:

CSS vous pouvez ajouter d'autres types de contrôles tels que select, date, password, etc

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 : inutile d'ajouter quoi que ce soit dans les contrôles sauf ng-required si c'est le cas

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

Essayez-le et tapez du texte sous votre contrôle, je le trouve vraiment pratique et génial.

2
Ali Adravi

Je sais que c’est un très vieux fil de questions-réponses quand je n’ai pas entendu le nom d’AngularJS lui-même :-)

Mais pour les autres qui atterrissent sur cette page à la recherche de la validation des formulaires Angular + Bootstrap de manière propre et automatisée, j’ai écrit un assez petit module permettant d’obtenir la même chose sans modifier le code HTML ou Javascript dans aucun des cas. forme.

Checkout Validation de Bootstrap Angular .

Voici les trois étapes simples:

  1. Installer via Bower bower install bootstrap-angular-validation --save
  2. Ajoutez le fichier de script <script src="bower_components/bootstrap-angular-validation/dist/bootstrap-angular-validation.min.js"></script>
  3. Ajoutez la dépendance bootstrap.angular.validation À votre application et c'est tout !!

Cela fonctionne avec Bootstrap 3 et jQuery est non requis .

Ceci est basé sur le concept de validation jQuery. Ce module fournit une validation supplémentaire et des messages génériques communs en cas d'erreur de validation.

1
Shashank Agrawal
<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
Ajay Kumar

Il est difficile de dire avec certitude sans un violon, mais l'examen du code angular.js ne remplace pas les classes, il ajoute et supprime le sien. Ainsi, toutes les classes bootstrap (ajoutées dynamiquement par bootstrap UI)) ne doivent pas être modifiées de manière angulaire.

Cela dit, il n’a aucun sens d’utiliser la fonctionnalité JS de Bootstrap pour la validation en même temps que Angular - utilisez uniquement Angular. Je vous suggérerais d’employer le bootstrap = styles et le angular JS, c’est-à-dire, ajoutez les classes bootstrap css à vos éléments à l’aide d’une directive de validation personnalisée.

1
Marc