In meinem aktuellen Projekt haben wir vor kurzem von AngularJS 1.2 auf 1.3 gewechselt. Abgesehen von einigen wenigen Änderungen war das Upgrade recht trivial. Nachdem wir jedoch in das Changelog eingetaucht waren, stellten wir fest, dass sich die Art und Weise, wie AngularJS die Formularvalidierung handhabt, drastisch geändert hat. Da wir an einer Anwendung auf der grünen Wiese arbeiten, beschlossen wir, dass es die Mühe wert war, die Validierungslogik neu zu schreiben. Das Hauptargument dafür war, dass die Validierung, die wir hatten, durch den Einsatz der neuen Validierungspipeline drastisch vereinfacht werden konnte. Dieser Artikel richtet sich an AngularJS-Entwickler, die sich für die neue Validierungspipeline von AngularJS 1.3 interessieren. Abgesehen von einer kleinen Einführung wird sich dieser Artikel nicht mit allen Aspekten der Validierung von Formularen befassen. Ich werde 2 verschiedene Fälle vorstellen, in denen wir eigene Lösungen entwickeln mussten:
- Anzeige zusätzlicher Informationen nach erfolgreicher Validierung
- Überprüfung der Gleichheit mehrerer Passwortfelder
Was hat sich geändert?
Während wir in AngularJS 1.2 $parsers und $formatters für die Formularvalidierung verwenden konnten, führt AngularJS 1.3 das Konzept von $validators und $asyncValidators ein. Wie wir aus den Namen ableiten können, ist letzteres für serverseitige Validierungen unter Verwendung von HTTP-Aufrufen und ersteres für Validierungen auf der Client-Seite. Alle Validatoren sind Direktiven, die für ein bestimmtes ngModel registriert werden, indem sie entweder zu ngModel.$validators oder ngModel.$asyncValidators hinzugefügt werden. Bei der Validierung werden alle $validators vor der Ausführung der $asyncValidators ausgeführt. AngularJS 1.3 nutzt auch die HTML5-Validierungs-API, wo immer dies möglich ist. Für jedes der HTML5-Validierungsattribute bietet AngularJS eine Direktive. Fügen Sie z.B. für minlength Ihrem Eingabefeld ng-minlength hinzu, um die Prüfung der Mindestlänge zu integrieren. Wenn es um die Anzeige von Fehlermeldungen geht, können wir uns auf die Eigenschaft $error eines ngModels verlassen. Immer, wenn einer der Validierer fehlschlägt, wird der Name des Validierers zur Eigenschaft $error hinzugefügt. Mit dem brandneuen Modul ngMessages können wir dann ganz einfach spezifische Fehlermeldungen je nach Art des Validators anzeigen.
Anzeige zusätzlicher Informationen nach erfolgreicher Validierung
Die Implementierung der neuen Validierungspipeline war mit einigen Herausforderungen verbunden. Die größte war, dass wir eine ganze Reihe von Anwendungsfällen hatten, in denen wir nach erfolgreicher Überprüfung eines Feldes einige vom Webservice zurückgegebene Daten anzeigen wollten. Im Folgenden werde ich erörtern, wie wir dies gelöst haben. Die Direktive selbst ist sehr einfach und führt lediglich die folgenden Schritte aus:
- Löschen Sie die neben dem Feld angezeigten Daten. Wenn der Benutzer bereits Text eingegeben hat und die Überprüfung erfolgreich war, werden die Daten aus dem Überprüfungsaufruf neben dem Eingabefeld angezeigt. Wenn der Benutzer den Wert des Eingabefeldes ändern würde und die Überprüfung nicht korrekt durchgeführt werden könnte, wären die neben dem Feld angezeigten Daten veraltet. Um dies zu verhindern, löschen wir zunächst die neben dem Feld angezeigten Daten zu Beginn der Überprüfung.
- Validieren Sie den Inhalt mit Hilfe der HelloResource gegen den Webdienst. Neben der Rückgabe des Versprechens, das uns die Ressource gibt, rufen wir die Methode callback() auf, wenn das Versprechen erfolgreich aufgelöst wurde.
- Anzeigen der vom HTTP-Aufruf zurückgegebenen Daten mit einer Callback-Methode
[javascript] 'use strict'; angular.module('angularValidators') .directive('validatorWithCallback', function (HelloResource) { zurück { erfordern: 'ngModel', link: function (scope, element, attrs, ngModel) { function callback(response) {} ngModel.$asyncValidators.validateWithCallback = function (modelValue, viewValue) { callback(''); var value = modelValue || viewValue; return HelloResource.get({Name: Wert}).$promise.then(function (response) { callback(Antwort); }); }; } }; }); [/javascript] Wir können den Validator zu unserer Eingabe hinzufügen, indem wir das Attribut validator-with-callback zu der Eingabe hinzufügen, die wir validieren möchten. [html] <form name="form"> <input type="text" name="name" ng-model="name" required validator-with-callback /> </form> [/html]
Implementierung von clear und callback
Da diese Direktive unabhängig von einem bestimmten ngModel sein soll, müssen wir einen Weg finden, das ngModel an die Direktive zu übergeben. Um dies zu erreichen, fügen wir dem Attribut validator-with-callback einen Wert hinzu. Außerdem ändern wir den Wert des Attributs ng-model in name.value. Warum dies erforderlich ist, werden wir später erklären. Zum Schluss fügen wir noch ein div hinzu, das nur angezeigt wird, wenn das Formularelement gültig ist, und wir legen fest, dass es den Wert von name.detail anzeigt.
[html]
<form name="form">
<input type="text" name="name" ng-model="name.value" required validator-with-callback="name" />
<div ng-if="form.name.$valid">{{name.detail}}</div>
</form>
[/html]
Die Methode $eval von scope kann verwendet werden, um das Objekt über den Wert des Attributs aufzulösen. Die Anzeige der Daten funktioniert nicht, wenn wir einfach ein beliebiges scoped Objekt (z.B. $scope.data) liefern und überschreiben. Wir müssen ein scoped object name hinzufügen, das 2 Eigenschaften enthält: value und detail. Hinweis: Die Namensgebung ist nicht wichtig.
Überprüfung der Gleichheit mehrerer Passwortfelder
Das zweite Beispiel, das wir zeigen möchten, ist ein synchroner Validator, der die Werte von 2 verschiedenen Feldern prüft. Der Anwendungsfall, den wir dafür hatten, waren 2 Passwortfelder, die gleich sein mussten.
Anforderungen
- (In)validieren Sie beide Felder, wenn der Benutzer den Wert eines der beiden Felder ändert und sie (nicht) gleich sind
- Der Validator validiert das Feld erfolgreich, wenn die zweite Eingabe nicht berührt wurde oder der Wert der zweiten Eingabe leer ist.
- Nur 1 Fehlermeldung anzeigen und nur, wenn keine anderen Validatoren (required & min-length) ungültig sind
Implementierung
Wir beginnen mit der Erstellung von 2 verschiedenen Formularelementen mit einer erforderlichen und einer ng-minlength-Prüfung. Außerdem fügen wir dem Formular eine Schaltfläche hinzu, um zu zeigen, wie das Aktivieren/Deaktivieren der Schaltfläche in Abhängigkeit von der Gültigkeit des Formulars funktioniert. Beide Passwortfelder haben auch die validate-must-equal-to="other_fieldname" Attribut. Dies zeigt an, dass wir den Wert dieses Feldes mit dem durch das Attribut definierten Feld abgleichen möchten. Außerdem fügen wir ein Attribut form-name="form" hinzu, um den Namen des Formulars an unsere Direktive zu übergeben. Dies wird benötigt, um die zweite Eingabe in unserem Formular zu validieren, ohne den Formularnamen innerhalb der Direktive fest zu kodieren und somit diese Direktive völlig unabhängig von Formular- und Feldnamen zu machen. Abschließend blenden wir auch die Container für die Fehleranzeige bedingt ein oder aus. Für die Fehler im Zusammenhang mit dem ersten Eingabefeld legen wir außerdem fest, dass es nicht angezeigt werden soll, wenn der Fehler notEqualTo durch unsere Direktive gesetzt wurde. Dadurch wird sichergestellt, dass kein leeres Div angezeigt wird, wenn unser Validator das erste Feld für ungültig erklärt. [html] <form name="form"> <input type="password" name="password" ng-model="password" required ng-minlength="8" validate-must-equal-to="password2" form-name="form" /> <div ng-messages="form.password.$error" ng-if="form.password.$touched && form.password.$invalid && !form.password.$error.notEqualTo"> <div ng-message="required">Dieses Feld ist erforderlich</div> <div ng-message="minlength">Ihr Passwort muss mindestens 8 Zeichen lang sein</div> </div> <input type="password" name="password2" ng-model="password2" required ng-minlength="8" validate-must-equal-to="password" form-name="form" /> <div ng-messages="form.password2.$error" ng-if="form.password2.$touched && form.password2.$invalid"> <div ng-message="required">Dieses Feld ist erforderlich</div> <div ng-message="minlength">Ihr Passwort muss mindestens 8 Zeichen lang sein</div> </div> Die Schaltfläche Absenden wird nur aktiviert, wenn das gesamte Formular gültig ist. <button ng-disabled="form.$invalid">Absenden</button> </form> [/html] Der Validator selbst ist wiederum sehr kompakt. Im Grunde wollen wir nur den Wert aus der Eingabe abrufen und ihn an eine isEqualToOther-Methode übergeben, die einen booleschen Wert zurückgibt. Zu Beginn der Link-Methode prüfen wir auch, ob das Attribut form-name vorhanden ist. Wenn nicht, geben wir einen Fehler aus. Wir tun dies, um jedem Entwickler, der diese Richtlinie wiederverwendet, mitzuteilen, dass diese Richtlinie den Formularnamen benötigt, um korrekt zu funktionieren. Leider gibt es im Moment keine andere Möglichkeit, das zusätzliche obligatorische Attribut mitzuteilen. [javascript] 'use strict'; angular.module('angularValidators') .directive('validateMustEqualTo', function () { return { require: 'ngModel', link: function (scope, element, attrs, ngModel) { if (.isUndefined(attrs.formName)) { throw 'Damit diese Direktive korrekt funktioniert, müssen Sie das Attribut form-name angeben'; } function isEqualToOther(value) { ...ausgelassen... } ngModel.$validators.notEqualTo = function (modelValue, viewValue) { var value = modelValue || viewValue; return isEqualToOther(value); }; } }; }); [/javascript] Die Methode isEqualToOther selbst bewirkt Folgendes:
- Rufen Sie das andere Eingabeformularelement ab
- Wirft einen Fehler aus, wenn sie nicht gefunden wird, was wiederum bedeutet, dass diese Direktive nicht wie vorgesehen funktioniert.
- Rufen Sie den Wert aus der anderen Eingabe ab und validieren Sie das Feld, wenn die Eingabe nicht berührt wurde oder der Wert leer ist
- Vergleichen Sie beide Werte
- Legen Sie die Gültigkeit des anderen Feldes in Abhängigkeit vom Vergleich fest
- Gibt den Vergleich zurück, um das Feld, mit dem diese Direktive verknüpft ist, zu (in)validieren
[javascript] 'use strict'; angular.module('angularValidators') .directive('validateMustEqualTo', function () { return { require: 'ngModel', link: function (scope, element, attrs, ngModel) { ...omitted... function isEqualToOther(Wert) { var otherInput = scope[attrs.formName][attrs.validateMustEqualTo]; wenn (.isUndefined(otherInput)) { throw 'Cannot retrieve the second field to compare with from the scope'; } var otherValue = otherInput.$modelValue || otherInput.$viewValue; if (otherInput.$untouched || .isEmpty(otherValue)) { true zurückgeben; } var isEqual = (Wert === andererWert); otherInput.$setValidity('notEqualTo', isEqual); return isEqual; } ngModel.$validators.notEqualTo = function (modelValue, viewValue) { ...omitted... }; } }; }); [/javascript]
Alternative Lösung
Eine alternative Lösung zur Direktive validate-must-equal-to könnte darin bestehen, eine Direktive zu implementieren, die beide Kennwortfelder kapselt und eine scoped-Funktion enthält, die die Validierung mit ng-blur für die Felder oder $watch für beide Eigenschaften durchführt. Bei diesem Ansatz wird jedoch nicht die sofort einsatzbereite Validierungspipeline verwendet, so dass wir die Logik in der ng-disabled-Schaltfläche des Formulars erweitern müssten, damit der Benutzer das Formular abschicken kann.
Fazit
AngularJS 1.3 führt eine neue Validierungspipeline ein, die unglaublich einfach zu verwenden ist. Bei fortgeschritteneren Validierungsregeln wird jedoch deutlich, dass bestimmte Funktionen (wie der Callback-Mechanismus) fehlen, für die wir eigene Lösungen finden mussten. In diesem Artikel zeigen wir Ihnen 2 verschiedene Validierungsfälle, die die Standard-Pipeline erweitern.
Demo-Anwendung
Ich habe eine eigenständige Demo-Anwendung erstellt, die von GitHub geklont werden kann. Diese Demo enthält sowohl Validatoren als auch Karma-Tests, die alle verschiedenen Szenarien abdecken. Sie können diesen Code gerne verwenden und nach Belieben verändern.
Verfasst von
Marc Rooding
Unsere Ideen
Weitere Blogs
Contact



