Convalida i campi dopo che l'utente ha lasciato un campo


92

Con AngularJS, posso utilizzare ng-pristineo ng-dirtyper rilevare se l'utente è entrato nel campo. Tuttavia, desidero eseguire la convalida lato client solo dopo che l'utente ha lasciato l'area del campo. Questo perché quando un utente entra in un campo come e-mail o telefono, riceverà sempre un errore fino a quando non avrà completato la digitazione completa dell'e-mail e questa non è un'esperienza utente ottimale.

Esempio


AGGIORNARE:

Angular ora viene fornito con un evento di sfocatura personalizzato: https://docs.angularjs.org/api/ng/directive/ngBlur


3
Immagino che questo sia qualcosa che angularjs avrebbe dovuto supportare ...
csg

Può essere. La convalida del modulo incorporata funziona abbastanza bene che non so se vorrei che fosse integrata. Dopo tutto, la maggior parte dei casi d'uso voglio che Angular convalidi immediatamente. cioè in un campo numerico, desidero che il modulo venga invalidato immediatamente se l'utente inizia a digitare lettere.
mcranston18

11
la convalida sulla sfocatura è comune da 15 anni ...
Olivvv

Risposte:


75

Angular 1.3 ora ha ng-model-options e puoi impostare l'opzione su, { 'updateOn': 'blur'}ad esempio, e puoi persino annullare il rimbalzo, quando l'uso sta digitando troppo velocemente o vuoi salvare alcune costose operazioni DOM (come la scrittura di un modello a più posizioni DOM e non vuoi che un ciclo $ digest avvenga su ogni tasto premuto)

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

Per impostazione predefinita, qualsiasi modifica al contenuto attiverà un aggiornamento del modello e la convalida del modulo. È possibile sovrascrivere questo comportamento utilizzando la direttiva ngModelOptions per eseguire il binding solo all'elenco di eventi specificato. Ie ng-model-options = "{updateOn: 'blur'}" verrà aggiornato e convalidato solo dopo che il controllo perde il focus. È possibile impostare diversi eventi utilizzando un elenco delimitato da spazi. Ie ng-model-options = "{updateOn: 'mousedown blur'}"

E antirimbalzo

È possibile ritardare l'aggiornamento / convalida del modello utilizzando la chiave antirimbalzo con la direttiva ngModelOptions. Questo ritardo si applicherà anche a parser, validatori e flag di modello come $ dirty o $ pristine.

Ie ng-model-options = "{debounce: 500}" aspetterà mezzo secondo dall'ultima modifica del contenuto prima di attivare l'aggiornamento del modello e la convalida del modulo.


2
Nella maggior parte dei casi questo non risolve il problema, perché probabilmente desideri ancora ricevere ng-changeeventi prima che l'utente lasci il campo. Ad esempio, nel mio caso richiedo nuovi dati dalla mia API non appena il campo di input è valido, ma non voglio mostrare un messaggio di errore non appena non è valido - voglio farlo solo quando lo è non valido e si è verificato l'evento sfocatura.
Tom

@ Tom, sto testando 1.3.0 rc-3e non si attivachange fino a quando blur, controlla questo: plnkr.co/edit/RivVU3r7xfHO1hUybqMu?p=preview
Gill Bates

Questa dovrebbe essere davvero la risposta migliore e accettata. Perché aggiungere una direttiva, quando puoi aggiungere una singola proprietà?
Dan Hulton

92

Dalla versione 1.3.0 questo può essere fatto facilmente $touched, il che è vero dopo che l'utente ha lasciato il campo.

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

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


1
Ciò non eseguirà comunque la convalida dopo ogni key-up? OP ha dichiarato di voler eseguire la convalida solo dopo che l'utente ha lasciato il campo.
comecme

@comecme sì, il valore predefinito ng-model-options è updateOn: 'default'che significa, per gli input, key up e per le selezioni, al cambio
pocesar

5
Il grande difetto qui, tuttavia, è che non avrai la stessa esperienza di convalida se torni in campo. Sei tornato al punto di partenza, dove si convalida a ogni pressione di tasto, poiché è già impostato su toccato. Sono sorpreso che questo abbia ottenuto così tanti voti positivi.
dudewad

3
@dudewad è corretto, ma penso che sia il comportamento desiderato in termini di UX: se torni in un campo non valido, il messaggio di errore dovrebbe persistere finché il valore non è valido, non finché non hai digitato la prima lettera.
danbars

@dudewad Non aiuterebbe l'utente a convalidare su ogni stampa dopo aver già saputo che un campo non era valido dopo la prima volta che lo ha compilato? Una volta che sanno che il campo non è valido, è probabile che vogliano sapere rapidamente quando il campo è valido e la convalida a ogni pressione di un tasto velocizzerebbe il processo?
Alan Dunning

32

Ho risolto questo problema espandendo ciò che ha suggerito @jlmcdonald. Ho creato una direttiva che verrebbe automaticamente applicata a tutti gli elementi di input e di selezione:

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

Questo aggiungerà has-focuse has-visitedclassi a vari elementi mentre l'utente si concentra / visita gli elementi. È quindi possibile aggiungere queste classi alle regole CSS per mostrare errori di convalida:

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

Funziona bene in quanto gli elementi ottengono ancora $ proprietà non valide, ecc., Ma il CSS può essere utilizzato per offrire all'utente un'esperienza migliore.


2
Perché è richiesto "? NgModel" anziché semplicemente "ngModel"? Capisco che sia necessario che ngModel sia presente, ma perché il punto interrogativo?
Noremac

2
Grazie per l'idea. Dovrebbe sottolineare che richiede jQuery (penso - non potrei vedere un .closest () in jql).
iandotkelly

1
Non tutti gli elementi <input> sono supportati da ngModel (ad esempio input type = submit). Il ? richiede ngModel solo sugli elementi che lo supportano.
chmanie

5
Ad essere onesti, non dovresti deviare dal modo angolare di impostare gli stati dei campi di input come formName.inputName.$dirty. Suggerirei di modificare la direttiva per scrivere un booleano simile formName.inputName.$focused, piuttosto che utilizzare nomi di classe per sfocatura e booleani per la convalida.
Tom

17

Sono riuscito a farlo con un po 'di CSS piuttosto semplice. Ciò richiede che i messaggi di errore siano fratelli dell'input a cui si riferiscono e che abbiano una classe di error.

:focus ~ .error {
    display:none;
}

Dopo aver soddisfatto questi due requisiti, questo nasconderà qualsiasi messaggio di errore relativo a un campo di input focalizzato, qualcosa che penso che angularjs dovrebbe fare comunque. Sembra una svista.


2
Ahh. Questo è un modo intelligente di procedere. Lo preferisco di gran lunga rispetto alla scrittura di javascript per farlo.
ponte

5
Lo svantaggio di questo approccio è che quando qualcuno corregge un errore l'errore non è più visibile, il che non è l'ideale. L'ideale sarebbe che l'errore non venga visualizzato fino alla sfocatura ma non scompaia finché non viene corretto.
Chris Nicola

4

Questo sembra essere implementato come standard nelle versioni più recenti di angular.

Le classi ng-untouched e ng-touched vengono impostate rispettivamente prima e dopo che l'utente si è concentrato su un elemento convalidato.

CSS

input.ng-touched.ng-invalid {
   border-color: red;
}

4

Per quanto riguarda la soluzione di @ lambinator ... stavo ottenendo il seguente errore in angular.js 1.2.4:

Errore: [$ rootScope: inprog] $ digest già in corso

Non sono sicuro di aver fatto qualcosa di sbagliato o se si tratta di un cambiamento in Angular, ma la rimozione delle scope.$applyistruzioni ha risolto il problema e le classi / stati vengono ancora aggiornati.

Se visualizzi anche questo errore, prova quanto segue:

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

2

Potrebbe funzionare per te scrivere una direttiva personalizzata che racchiuda il metodo javascript blur () (ed esegue una funzione di convalida quando attivata); c'è un problema Angular che ne ha uno di esempio (oltre a una direttiva generica che può legarsi ad altri eventi non supportati nativamente da Angular):

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

Se non vuoi seguire quella strada, l'altra opzione sarebbe quella di impostare $ watch sul campo, attivando nuovamente la convalida quando il campo è compilato.


2

Per riprendere ulteriormente le risposte fornite, puoi semplificare la codifica degli input utilizzando le pseudo-classi CSS3 e contrassegnando solo i campi visitati con una classe per ritardare la visualizzazione degli errori di convalida fino a quando l'utente non ha perso il focus sul campo:

(L'esempio richiede 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>

2

basato sulla risposta di @nicolas .. Pure CSS dovrebbe essere il trucco, mostrerà solo il messaggio di errore su blur

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

Questo va bene, ma il messaggio di errore scomparirà quando l'utente torna indietro nel campo, il che non è l'ideale.
Bennett McElwee

2

Ecco un esempio che utilizza ng-messages (disponibile in angular 1.3) e una direttiva personalizzata.

Il messaggio di convalida viene visualizzato sulla sfocatura per la prima volta che l'utente lascia il campo di input, ma quando corregge il valore, il messaggio di convalida viene rimosso immediatamente (non più sulla sfocatura).

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. Non dimenticare di scaricare e includere ngMessages nel tuo modulo:

var myApp = angular.module('myApp', ['ngMessages']);

1

ng-model-options in AngularJS 1.3 (beta al momento della stesura di questo documento) è documentato per supportare {updateOn: 'blur'}. Per le versioni precedenti, qualcosa come il seguente ha funzionato per me:

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

Con un modello come questo:

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

1

Usa stato campo $ toccato Il campo è stato toccato per questo come mostrato nell'esempio sotto.

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

0

Puoi impostare dinamicamente la classe css has-error (supponendo che tu stia usando bootstrap) usando ng-class e una proprietà nell'ambito del controller associato:

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>

Controller:

$scope.badEmailAddress = false;

$scope.emailBlurred = function (isValid) {
    $scope.badEmailAddress = !isValid;
};

0

Se usi bootstrap 3 e lesscss puoi abilitare la convalida della sfocatura con il seguente frammento di meno:

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

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

.has-feedback > :focus {
  & {
    .form-control-focus();
  }
}

0

out Ho usato una direttiva. Ecco il codice:

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

Tutto il mio controllo di input ha un elemento span come elemento successivo, che è dove viene visualizzato il mio messaggio di convalida e quindi la direttiva come attributo viene aggiunta a ciascun controllo di input.

Ho anche (opzionale) .has-focus e has-visiting css class nel mio file css che vedi essere referenziato nella direttiva.

NOTA: ricordarsi di aggiungere "on-blur-val" esattamente in questo modo al controllo di input senza gli apostrofi


0

Usando ng-focus puoi raggiungere il tuo obiettivo. devi fornire ng-focus nel tuo campo di input. E mentre scrivi i tuoi derivati ​​ng-show devi scrivere anche una logica non uguale. Come il codice seguente:

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


0

Possiamo usare le funzioni onfocus e onblur. Sarebbe semplice e migliore.

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

Prova qui:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.