Stimmen Sie das Formularüberprüfungsstil für Angular.js und Bootstrap ab


72

Ich benutze Angular mit Bootstrap. Hier ist der Code als Referenz:

<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 hat Stile für ungültige Felder in Form von input:invalid {.... }; Diese treten ein, wenn das Feld leer ist. Jetzt habe ich auch einige Musterübereinstimmungen über Angular. Dies führt zu seltsamen Fällen, wenn ": invalid" deaktiviert ist, ".ng-invalid" jedoch aktiviert ist. Daher müsste ich die Bootstrap-CSS-Klassen für die Klasse ".ng-invalid" erneut implementieren.

Ich sehe zwei Möglichkeiten, habe aber Probleme mit beiden

  • Lassen Sie Angular einen benutzerdefinierten Klassennamen anstelle von "ng-valid" verwenden (ich weiß nicht, wie das geht).
  • Deaktivieren Sie die HTML5-Validierung (ich dachte, dass dies das Attribut "novalidate" im Formular-Tag tun sollte, konnte es aber aus irgendeinem Grund nicht zum Laufen bringen).

Die Angular-Bootstrap-Direktiven decken das Styling nicht ab.


3
novalidate sollte "die native Formularüberprüfung des Browsers deaktivieren" - Formulardokumente
Mark Rajcok

Antworten:


92

Verwenden Sie die "Fehler" -Klasse von Bootstrap für das Styling. Sie können weniger Code schreiben.

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

BEARBEITEN: Wie andere Antworten und Kommentare zeigen - in Bootstrap 3 ist die Klasse jetzt "has-error", nicht "error".


53
Oder wenn Sie Bootstrap 3ng-class="{'has-error': myForm.name.$invalid}"
Bibstha

17
Sie können auch hinzufügen && myForm.name.$dirty, damit der Validierungsstil erst angezeigt wird, nachdem der Benutzer mit dem Formularsteuerelement interagiert hat.
MWillis

@bibstha was ist mit help-inline für bs3?
SuperUberDuper

5
Dies funktioniert, ist aber ein enormer Aufwand und macht Vorlagen unglaublich ausführlich. Ich suche einen saubereren Weg.
BenCr

Was ich fand, sobald ich ein paar Antworten nach unten gescrollt hatte.
BenCr

47

Die Klassen haben sich in Bootstrap 3 geändert:

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

Beachten Sie die Anführungszeichen 'has-error'und 'has-success': Es dauerte eine Weile, bis ...


Hat sich jemand ng-class="(form.email.$invalid ? 'has-error' : 'has-success')"an die Arbeit gemacht?
Kristianlm

4
Um zu vermeiden, dass die Eingaben direkt nach dem Laden der Seite ungültig erscheinen, sollten Sie die Eigenschaft $ dirty überprüfen, um festzustellen, ob das Feld bereits bearbeitet wurde: {'has-error': form.email.$dirty && form.email.$invalid, 'has-success': form.email.$dirty && !form.email.$invalid}Jetzt wird dieser Ausdruck jedoch so lang, dass er für Tippfehler anfällig und schwer lesbar ist und immer ist ähnlich sollte es also einen besseren Weg geben, nicht?
Mono68

1
Ich verwende eine Direktive, die dem Formular ein " Submission"
-Flag

1
@kristianlm hast du tryng-class = "{'has-error': form.email. $ invalid, 'has-success' :! form.email. $ invalid}" auf einem div ÜBER der Eingabe ...
malix

@malix das könnte funktionieren, aber ich wollte form.email nicht wiederholen müssen. $ ungültig.
Kristianlm

34

Eine andere Lösung: Erstellen Sie eine Direktive, die die has-errorKlasse gemäß einer untergeordneten Eingabe umschaltet .

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

und dann einfach in Vorlage verwenden

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

3
Ich denke, eine Richtlinie ist in diesem Fall die beste Lösung.
JKillian

2
Es ist erwähnenswert, dass jqlite die element[attribute]Selektorsyntax nicht unterstützt. Daher muss dies ein wenig geändert werden, wenn Sie jQuery nicht verwenden.
Tamlyn

1
Die Uhr wird niemals entsorgt, wenn kein Ausdruck
geliefert wird

3
Wäre es nicht besser, die NgModelController-Attribute direkt zu betrachten, anstatt sich die Klasse anzusehen? input.controller('ngModel').$invalidstatt input.hasClass('ng-invalid'). Ich hatte ein Problem damit, dass die CSS-Klasse nicht aktualisiert wurde, bevor $ watch ausgelöst wurde.
Thomas Wajs

@ Thomas Wajs ist absolut richtig. Die obige Lösung (sowie einige der anderen hier veröffentlichten) ist immer ein nicht synchronisierter Digest-Zyklus, da die Auswertung in der Mitte des Digest-Zyklus durchgeführt wird, bevor die ungültigen Klassen aktualisiert wurden. input.controller ('ngModel'). $ invalid behebt dieses Problem.
RonnBlack

22

Kleinere Verbesserung der Antwort von @ farincz . Ich bin damit einverstanden, dass eine Direktive hier der beste Ansatz ist, aber ich wollte sie nicht für jedes .form-groupElement wiederholen müssen, also habe ich den Code aktualisiert, damit er entweder dem .form-groupoder dem übergeordneten <form>Element hinzugefügt werden kann (wodurch er allen enthaltenen .form-groupElementen hinzugefügt wird) ):

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

Kleinere Verbesserung der Antwort von @Andrew Smith. Ich ändere Eingabeelemente und benutze requireSchlüsselwörter.

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

Muss in $ (Element) geändert werden. jqlite unterstützt die Suche nach Klassen nicht. (Ich habe versucht, eine Bearbeitung einzureichen, aber ich muss 6 Zeichen ändern ....)
Ben George

11

Vielen Dank an @farincz für eine großartige Antwort. Hier sind einige Änderungen, die ich vorgenommen habe, um sie an meinen Anwendungsfall anzupassen.

Diese Version enthält drei Anweisungen:

  • bs-has-success
  • bs-has-error
  • bs-has (eine Annehmlichkeit, wenn Sie die anderen beiden zusammen verwenden möchten)

Von mir vorgenommene Änderungen:

  • Es wurde eine Überprüfung hinzugefügt, um nur die Status anzuzeigen, wenn das Formularfeld fehlerhaft ist, dh sie werden erst angezeigt, wenn jemand mit ihnen interagiert.
  • Die übergebene Zeichenfolge wurde element.find()für diejenigen geändert, die jQuery nicht verwenden, da element.find()in Angulars jQLite nur das Suchen von Elementen anhand des Tagnamens unterstützt wird.
  • Unterstützung für Auswahlfelder und Textbereiche hinzugefügt.
  • Umhüllt das element.find()in a $timeout, um Fälle zu unterstützen, in denen die Elemente des Elements möglicherweise noch nicht im DOM gerendert wurden (z. B. wenn ein untergeordnetes Element des Elements mit markiert ist ng-if).
  • Der ifAusdruck wurde geändert , um die Länge des zurückgegebenen Arrays zu überprüfen ( if(input)aus der Antwort von @ farincz wird immer true zurückgegeben, da die Rückgabe von element.find()ein jQuery-Array ist).

Ich hoffe jemand findet das nützlich!

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

Verwendung:

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

Sie können auch das Laubenpaket von Guilherme installieren , das all dies zusammen bündelt.


1
Ich habe es auf Bower als "Angular-Bootstrap-Validierung" mit den Credits an Sie und @farincz veröffentlicht, hoffe es macht Ihnen nichts aus
Gui

2
Das ist cool. Ich stelle fest, dass Sie eine Funktion hinzugefügt haben, mit der Sie die Direktive auf Formularebene platzieren und durch die verschachtelten .form-groupElemente wiederholen können . Das ist schön, aber es funktioniert nur, wenn Sie jQuery einschließen, da die integrierte eckige jqlite-Implementierung von findnur das Suchen nach Tagname und nicht nach Selektor unterstützt. Möglicherweise möchten Sie zu diesem Zweck eine Notiz in der README-Datei hinzufügen.
Tom Spencer

Sie können verwenden form.$submitted, um Fehler nur beim Senden anzuzeigen. In den Angular-Dokumenten finden Sie hier ein Beispiel: docs.angularjs.org/guide/forms . Suchen Sie nach der Überschrift "Bindung an Form und Kontrollstatus".
Jeff Kilbride

4

Wenn das Styling das Problem ist, Sie aber die native Validierung nicht deaktivieren möchten, können Sie das Styling mit Ihrem eigenen, spezifischeren Stil überschreiben .

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

Kaskadieren Sie Ihre Probleme mit der CSS-Selektorspezifität!


2

Meine Verbesserung der folgenden Antwort von Jason Im fügt zwei neue Anweisungen hinzu: show-validation-error und 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);
                });

            }
        };
    });

})();

Die Show-Validation-Fehler können einem Fehlercontainer hinzugefügt werden, sodass der Container basierend auf der Gültigkeit eines Formularfelds angezeigt / ausgeblendet wird.

und der Show-Validierungsfehler zeigt oder verbirgt ein Element, das auf der Gültigkeit dieses Formularfelds für einen gegebenen Typ basiert.

Ein Beispiel für den Verwendungszweck:

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

Für die Validierungsfehler können Sie auch ngMessage
Sander Rijken

2

Ich denke, es ist zu spät, um zu antworten, aber ich hoffe, Sie werden es lieben:

Mit CSS können Sie andere Steuerelemente wie Auswahl, Datum, Kennwort usw. Hinzufügen

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 : Es ist nicht erforderlich, Steuerelemente hinzuzufügen, außer ng-require, falls dies der Fall ist

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

Probieren Sie es einfach aus und geben Sie Text in Ihr Steuerelement ein. Ich finde es sehr praktisch und großartig.


1

Ohne Geige ist es schwer zu sagen, aber wenn man sich den Code angle.js ansieht, ersetzt er keine Klassen - er fügt nur seine eigenen hinzu und entfernt sie. Daher sollten alle Bootstrap-Klassen (die dynamisch durch Bootstrap-UI-Skripte hinzugefügt werden) von Angular unberührt bleiben.

Es ist jedoch nicht sinnvoll, die JS-Funktionalität von Bootstrap zur Validierung gleichzeitig mit Angular zu verwenden - verwenden Sie nur Angular. Ich würde vorschlagen, dass Sie die Bootstrap-Stile und das eckige JS verwenden, dh die Bootstrap-CSS-Klassen mithilfe einer benutzerdefinierten Validierungsanweisung zu Ihren Elementen hinzufügen.


Sie haben Recht, das Deaktivieren der nativen Validierung ist der richtige Weg. Das habe ich aber nicht geschafft. Ich werde weiter suchen. Vielen Dank!
Ivan P

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

Ich weiß, dass dies ein sehr alter Frage-Antwort-Thread ist, wenn ich den Namen AngularJS selbst nicht gehört habe :-)

Aber für andere, die auf dieser Seite landen und auf saubere und automatisierte Weise nach einer Angular + Bootstrap-Formularvalidierung suchen, habe ich ein ziemlich kleines Modul geschrieben, um dasselbe zu erreichen, ohne HTML oder Javascript in irgendeiner Form zu ändern.

Checkout Bootstrap Angular Validation .

Es folgen die drei einfachen Schritte:

  1. Über Bower installieren bower install bootstrap-angular-validation --save
  2. Fügen Sie die Skriptdatei hinzu <script src="bower_components/bootstrap-angular-validation/dist/bootstrap-angular-validation.min.js"></script>
  3. Fügen Sie die Abhängigkeit bootstrap.angular.validationzu Ihrer Anwendung hinzu und fertig!

Dies funktioniert mit Bootstrap 3 und jQuery ist nicht erforderlich .

Dies basiert auf dem Konzept der jQuery-Validierung. Dieses Modul bietet einige zusätzliche Validierungs- und allgemeine generische Meldungen für Validierungsfehler.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.