Valider les champs après que l'utilisateur a quitté un champ

92

Avec AngularJS, je peux utiliser ng-pristineou ng-dirtypour détecter si l'utilisateur est entré dans le champ. Cependant, je souhaite effectuer une validation côté client uniquement après que l'utilisateur a quitté la zone de champ. En effet, lorsqu'un utilisateur saisit un champ tel que e-mail ou téléphone, il recevra toujours une erreur jusqu'à ce qu'il ait terminé de taper son e-mail complet, et ce n'est pas une expérience utilisateur optimale.

Exemple


METTRE À JOUR:

Angular est désormais livré avec un événement de flou personnalisé: https://docs.angularjs.org/api/ng/directive/ngBlur

mcranston18
la source
3
Je suppose que c'est quelque chose qu'angularjs aurait dû prendre en charge ...
csg
Peut être. La validation de formulaire intégrée fonctionne assez bien que je ne sais pas si je voudrais qu'elle soit intégrée. Après tout, la plupart des cas d'utilisation que je veux qu'Angular valide immédiatement. c'est-à-dire dans un champ numérique, je veux que le formulaire soit invalidé immédiatement si l'utilisateur commence à taper des lettres.
mcranston18
11
la validation sur le flou est courante depuis 15 ans ...
Olivvv

Réponses:

75

Angular 1.3 a maintenant ng-model-options, et vous pouvez définir l'option sur { 'updateOn': 'blur'}par exemple, et vous pouvez même anti-rebond, lorsque l'utilisation est de taper trop vite, ou que vous souhaitez enregistrer quelques opérations DOM coûteuses (comme l'écriture d'un modèle à plusieurs endroits DOM et vous ne voulez pas qu'un cycle $ digest se produise à chaque touche enfoncée)

https://docs.angularjs.org/guide/forms#custom-triggers et https://docs.angularjs.org/guide/forms#non-immediate-debounce-model-updates

Par défaut, toute modification du contenu déclenchera une mise à jour du modèle et une validation du formulaire. Vous pouvez remplacer ce comportement à l'aide de la directive ngModelOptions pour se lier uniquement à la liste d'événements spécifiée. Ie ng-model-options = "{updateOn: 'blur'}" se mettra à jour et ne validera que lorsque le contrôle perd le focus. Vous pouvez définir plusieurs événements à l'aide d'une liste délimitée par des espaces. Ie ng-model-options = "{updateOn: 'mousedown blur'}"

Et rebondir

Vous pouvez retarder la mise à jour / la validation du modèle en utilisant la clé anti-rebond avec la directive ngModelOptions. Ce délai s'appliquera également aux analyseurs, validateurs et indicateurs de modèle tels que $ dirty ou $ pristine.

Ie ng-model-options = "{debounce: 500}" attendra une demi-seconde depuis le dernier changement de contenu avant de déclencher la mise à jour du modèle et la validation du formulaire.

pocésar
la source
2
Dans la plupart des cas, cela ne résout pas le problème, car vous souhaitez probablement toujours recevoir des ng-changeévénements avant que l'utilisateur ne quitte le champ. Par exemple, dans mon cas, je demande de nouvelles données à mon API dès que le champ de saisie est valide, mais je ne veux pas afficher de message d'erreur dès qu'il est invalide - je veux le faire seulement quand c'est invalide et l'événement de flou s'est produit.
Tom
@Tom Je teste 1.3.0 rc-3et il ne se déclenche pas changeavant blur, vérifiez ceci: plnkr.co/edit/RivVU3r7xfHO1hUybqMu?p=preview
Gill Bates
Cela devrait vraiment être la meilleure réponse acceptée. Pourquoi ajouter une directive, alors que vous pouvez ajouter une seule propriété?
Dan Hulton
92

À partir de la version 1.3.0, cela peut facilement être fait avec $touched, ce qui est vrai après que l'utilisateur a quitté le terrain.

<p ng-show="form.field.$touched && form.field.$invalid">Error</p>

https://docs.angularjs.org/api/ng/type/ngModel.NgModelController

user1506145
la source
1
Cela n'exécuterait-il pas encore la validation après chaque key-up? OP a déclaré qu'il voulait faire la validation seulement après que l'utilisateur ait quitté le terrain.
comecme
@comecme oui, le ng-model-options par défaut est updateOn: 'default'ce qui signifie, pour les entrées, la touche vers le haut et pour les sélections, en cas de changement
pocesar
5
Le gros défaut ici, cependant, est que vous n'aurez pas la même expérience de validation si vous revenez sur le terrain. Vous êtes de retour à la case départ, où il valide à chaque frappe, car il est déjà réglé sur touché. Je suis surpris que cela ait reçu autant de votes positifs.
dudewad le
3
@dudewad c'est correct, mais je pense que c'est le comportement souhaité en termes d'UX - si vous revenez à un champ invalide, le message d'erreur devrait persister jusqu'à ce que la valeur soit valide, pas avant que vous n'ayez tapé la première lettre.
danbars
@dudewad Cela n'aiderait-il pas l'utilisateur à valider à chaque pression après qu'il sache déjà qu'un champ était invalide après la première fois qu'il l'a rempli? Une fois qu'ils savent que le champ n'est pas valide, il est probable qu'ils voudraient savoir rapidement quand le champ est valide et la validation à chaque pression de touche accélérerait peut-être ce processus?
Alan Dunning le
32

J'ai résolu ce problème en développant ce que @jlmcdonald suggérait. J'ai créé une directive qui serait automatiquement appliquée à toutes les entrées et sélectionnez les éléments:

var blurFocusDirective = function () {
    return {
        restrict: 'E',
        require: '?ngModel',
        link: function (scope, elm, attr, ctrl) {
            if (!ctrl) {
                return;
            }

            elm.on('focus', function () {
                elm.addClass('has-focus');

                scope.$apply(function () {
                    ctrl.hasFocus = true;
                });
            });

            elm.on('blur', function () {
                elm.removeClass('has-focus');
                elm.addClass('has-visited');

                scope.$apply(function () {
                    ctrl.hasFocus = false;
                    ctrl.hasVisited = true;
                });
            });

            elm.closest('form').on('submit', function () {
                elm.addClass('has-visited');

                scope.$apply(function () {
                    ctrl.hasFocus = false;
                    ctrl.hasVisited = true;
                });
            });

        }
    };
};

app.directive('input', blurFocusDirective);
app.directive('select', blurFocusDirective);

Cela ajoutera des classes à divers éléments au fur has-focuset has-visitedà mesure que l'utilisateur se concentre / visite les éléments. Vous pouvez ensuite ajouter ces classes à vos règles CSS pour afficher les erreurs de validation:

input.has-visited.ng-invalid:not(.has-focus) {
    background-color: #ffeeee;   
}

Cela fonctionne bien dans la mesure où les éléments obtiennent toujours des propriétés $ invalides, etc., mais le CSS peut être utilisé pour donner à l'utilisateur une meilleure expérience.

lambinator
la source
2
Pourquoi «? NgModel» est-il requis au lieu de simplement «ngModel»? Je comprends que ngModel soit requis, mais pourquoi le point d'interrogation?
Noremac
2
Merci pour l'idée. Doit souligner qu'il nécessite jQuery (je pense - ne pouvait pas voir un .closest () dans jql).
iandotkelly
1
Tous les éléments <input> ne sont pas pris en charge par ngModel (par exemple, input type = submit). Le ? nécessite ngModel uniquement sur les éléments qui le prennent en charge.
chmanie
5
Pour être honnête, vous ne devez pas vous écarter de la méthode angulaire de définition des états des champs de saisie comme formName.inputName.$dirty. Je suggérerais de modifier la directive pour écrire un booléen similaire formName.inputName.$focused, plutôt que d'utiliser des noms de classe pour le flou et des booléens pour la validation.
Tom
17

J'ai réussi à le faire avec un peu de CSS assez simple. Cela nécessite que les messages d'erreur soient frères de l'entrée à laquelle ils se rapportent et qu'ils aient une classe de error.

:focus ~ .error {
    display:none;
}

Après avoir satisfait à ces deux exigences, cela masquera tout message d'erreur lié à un champ de saisie ciblé, ce que je pense qu'angularjs devrait faire de toute façon. Cela ressemble à un oubli.

Nicolas
la source
2
Ahh. C'est une manière intelligente de procéder. Je préfère de loin cela à l'écriture de javascript pour le faire.
pont
5
L'inconvénient de cette approche est que lorsque quelqu'un corrige une erreur, l'erreur n'est plus visible, ce qui n'est pas idéal. L'idéal serait que l'erreur n'apparaisse pas avant le flou, mais ne disparaisse pas tant qu'elle n'est pas corrigée.
Chris Nicola
4

Cela semble être implémenté en standard dans les nouvelles versions d'angular.

Les classes ng-untouched et ng-touched sont définies respectivement avant et après que l'utilisateur se soit concentré sur un élément validé.

CSS

input.ng-touched.ng-invalid {
   border-color: red;
}
Arg0n
la source
4

Concernant la solution de @ lambinator ... J'obtenais l' erreur suivante dans angular.js 1.2.4:

Erreur: [$ rootScope: inprog] $ digest déjà en cours

Je ne sais pas si j'ai fait quelque chose de mal ou s'il s'agit d'un changement dans Angular, mais la suppression des scope.$applyinstructions a résolu le problème et les classes / états sont toujours mis à jour.

Si vous voyez également cette erreur, essayez ce qui suit:

var blurFocusDirective = function () {
  return {
    restrict: 'E',
    require: '?ngModel',
    link: function (scope, elm, attr, ctrl) {
      if (!ctrl) {
        return;
      }
      elm.on('focus', function () {
        elm.addClass('has-focus');
        ctrl.$hasFocus = true;
      });

      elm.on('blur', function () {
        elm.removeClass('has-focus');
        elm.addClass('has-visited');
        ctrl.$hasFocus = false;
        ctrl.$hasVisited = true;
      });

      elm.closest('form').on('submit', function () {
        elm.addClass('has-visited');

        scope.$apply(function () {
          ctrl.hasFocus = false;
          ctrl.hasVisited = true;
        });
      });
    }
  };
};
app.directive('input', blurFocusDirective);
app.directive('select', blurFocusDirective);
fondeur
la source
2

Cela peut fonctionner pour vous d'écrire une directive personnalisée qui encapsule la méthode javascript blur () (et exécute une fonction de validation lorsqu'elle est déclenchée); il y a un problème Angular qui en a un exemple (ainsi qu'une directive générique qui peut se lier à d'autres événements non pris en charge nativement par Angular):

https://github.com/angular/angular.js/issues/1277

Si vous ne souhaitez pas emprunter cette voie, votre autre option serait de configurer $ watch sur le champ, déclenchant à nouveau la validation lorsque le champ est rempli.

jlmcdonald
la source
2

Pour aller plus loin sur les réponses données, vous pouvez simplifier le balisage d'entrée en utilisant des pseudo-classes CSS3 et en ne marquant que les champs visités avec une classe pour retarder l'affichage des erreurs de validation jusqu'à ce que l'utilisateur perd le focus sur le champ:

(L'exemple nécessite jQuery)

JavaScript

module = angular.module('app.directives', []);
module.directive('lateValidateForm', function () {
    return {
        restrict: 'AC',
        link: function (scope, element, attrs) {
            $inputs = element.find('input, select, textarea');

            $inputs.on('blur', function () {
                $(this).addClass('has-visited');
            });

            element.on('submit', function () {
                $inputs.addClass('has-visited');
            });
        }
    };
});

CSS

input.has-visited:not(:focus):required:invalid,
textarea.has-visited:not(:focus):required:invalid,
select.has-visited:not(:focus):required:invalid {
  color: #b94a48;
  border-color: #ee5f5b;
}

HTML

<form late-validate-form name="userForm">
  <input type="email" name="email" required />
</form>
Patrick Glandien
la source
2

basé sur la réponse @nicolas .. Du CSS pur devrait faire l'affaire, il affichera uniquement le message d'erreur sur le flou

<input type="email" id="input-email" required
               placeholder="Email address" class="form-control" name="email"
               ng-model="userData.email">
        <p ng-show="form.email.$error.email" class="bg-danger">This is not a valid email.</p>

CSS

.ng-invalid:focus ~ .bg-danger {
     display:none;
}
keithics
la source
C'est bien, mais le message d'erreur disparaîtra lorsque l'utilisateur cliquera de nouveau dans le champ, ce qui n'est pas idéal.
Bennett McElwee
2

Voici un exemple utilisant ng-messages (disponible dans angular 1.3) et une directive personnalisée.

Le message de validation est affiché sur le flou pour la première fois que l'utilisateur quitte le champ de saisie, mais lorsqu'il corrige la valeur, le message de validation est immédiatement supprimé (et non plus sur le flou).

JavaScript

myApp.directive("validateOnBlur", [function() {
    var ddo = {
        restrict: "A",
        require: "ngModel",
        scope: {},
        link: function(scope, element, attrs, modelCtrl) {
            element.on('blur', function () {
                modelCtrl.$showValidationMessage = modelCtrl.$dirty;
                scope.$apply();
            });
        }
    };
    return ddo;
}]);

HTML

<form name="person">
    <input type="text" ng-model="item.firstName" name="firstName" 
        ng-minlength="3" ng-maxlength="20" validate-on-blur required />
    <div ng-show="person.firstName.$showValidationMessage" ng-messages="person.firstName.$error">
        <span ng-message="required">name is required</span>
        <span ng-message="minlength">name is too short</span>
        <span ng-message="maxlength">name is too long</span>
    </div>
</form>

PS. N'oubliez pas de télécharger et d'inclure ngMessages dans votre module:

var myApp = angular.module('myApp', ['ngMessages']);
Jurgemar
la source
1

ng-model-options dans AngularJS 1.3 (bêta à ce jour) est documenté pour prendre en charge {updateOn: 'blur'}. Pour les versions antérieures, quelque chose comme ce qui suit a fonctionné pour moi:

myApp.directive('myForm', function() {
  return {
    require: 'form',
    link: function(scope, element, attrs, formController) {
      scope.validate = function(name) {
        formController[name].isInvalid
            = formController[name].$invalid;
      };
    }
  };
});

Avec un modèle comme celui-ci:

<form name="myForm" novalidate="novalidate" data-my-form="">
<input type="email" name="eMail" required="required" ng-blur="validate('eMail')" />
<span ng-show="myForm.eMail.isInvalid">Please enter a valid e-mail address.</span>
<button type="submit">Submit Form</button>
</form>
Terence Bandoian
la source
1

Utiliser l'état du champ $ touched Le champ a été touché pour cela comme indiqué dans l'exemple ci-dessous.

<div ng-show="formName.firstName.$touched && formName.firstName.$error.required">
    You must enter a value
</div>
Kushal Sharma
la source
0

Vous pouvez définir dynamiquement la classe css has-error (en supposant que vous utilisez bootstrap) en utilisant ng-class et une propriété sur la portée du contrôleur associé:

plunkr: http://plnkr.co/edit/HYDlaTNThZE02VqXrUCH?p=info

HTML:

<div ng-class="{'has-error': badEmailAddress}">
    <input type="email" class="form-control" id="email" name="email"
        ng-model="email" 
        ng-blur="emailBlurred(email.$valid)">
</div>

Manette:

$scope.badEmailAddress = false;

$scope.emailBlurred = function (isValid) {
    $scope.badEmailAddress = !isValid;
};
Mike
la source
0

Si vous utilisez bootstrap 3 et lesscss, vous pouvez activer la validation du flou avec l'extrait de code less suivant:

:focus ~ .form-control-feedback.glyphicon-ok {
  display:none;
}

:focus ~ .form-control-feedback.glyphicon-remove {
  display:none;
}

.has-feedback > :focus {
  & {
    .form-control-focus();
  }
}
Stefan
la source
0

J'ai utilisé une directive. Voici le code:

app.directive('onBlurVal', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attrs, controller) {

            element.on('focus', function () {
                element.next().removeClass('has-visited');
                element.next().addClass('has-focus');
            });

            element.on('blur', function () {

                element.next().removeClass('has-focus');
                element.next().addClass('has-visited');
            });
        }
    }
})

Tout mon contrôle d'entrée a un élément span comme élément suivant, qui est l'endroit où mon message de validation est affiché et donc la directive en tant qu'attribut est ajoutée à chaque contrôle d'entrée.

J'ai également (facultatif) .has-focus et has-visité la classe css dans mon fichier css que vous voyez référencé dans la directive.

REMARQUE: n'oubliez pas d'ajouter 'on-blur-val' exactement de cette façon à votre contrôle d'entrée sans les apostrophes

user3676608
la source
0

En utilisant ng-focus, vous pouvez atteindre votre objectif. vous devez fournir ng-focus dans votre champ de saisie. Et pendant que vous écrivez vos dérivés ng-show, vous devez également écrire une logique différente. Comme le code ci-dessous:

<input type="text" class="form-control" name="inputPhone" ng-model="demo.phoneNumber" required ng-focus> <div ng-show="demoForm.inputPhone.$dirty && demoForm.inputPhone.$invalid && !demoForm.inputPhone.$focused"></div>

Rayon
la source
0

Nous pouvons utiliser les fonctions onfocus et onblur. Ce serait simple et meilleur.

<body ng-app="formExample">
  <div ng-controller="ExampleController">
  <form novalidate class="css-form">
    Name: <input type="text" ng-model="user.name" ng-focus="onFocusName='focusOn'" ng-blur="onFocusName=''" ng-class="onFocusName" required /><br />
    E-mail: <input type="email" ng-model="user.email" ng-focus="onFocusEmail='focusOn'" ng-blur="onFocusEmail=''" ng-class="onFocusEmail" required /><br />
  </form>
</div>

<style type="text/css">
 .css-form input.ng-invalid.ng-touched {
    border: 1px solid #FF0000;
    background:#FF0000;
   }
 .css-form input.focusOn.ng-invalid {
    border: 1px solid #000000;
    background:#FFFFFF;
 }
</style>

Essayez ici:

http://plnkr.co/edit/NKCmyru3knQiShFZ96tp?p=preview

Naveen Kumar
la source