Parametrisierbare Validierungs-Attribute

Mit Angular 2 Erstellen

Angular 2 bietet wie sein Vorgänger auch die Möglichkeit zur deklarativen Angabe von Validierungsregeln. Beispielsweise kann eine Anwendung mit den Attributen minlength und maxlength eine Längenbegrenzung für Felder festlegen:

<input [(ngModel)]="von" minlength="3" maxlength="30">

In diesem Beitrag zeige ich, wie man diese Möglichkeit für eigene Validierungsregeln nutzen kann. Dazu dazu genutzte Beispiel findet sich auch in meinem Demo-Projekt unter [1].

Validierungs-Regel implementieren

Eine Validierungsregel ist in Angular 2 zunächst lediglich eine Funktion, die ein Control-Objekt entgegen nimmt und ein Fehlerbeschreibungsobjekt retourniert. Das Fehlerbeschreibungsobjekt beinhaltet für jeden entdeckten Validierungsfehler eine Eigenschaft. Die Anwendung kann später den Schlüssel dieser Eigenschaften abfragen, um sich über den Ausgang der Validierung zu informieren. Konnte die Validierungsfunktion keinen Fehler entdecken, liefert sie ein leeres Objekt retour.

Eine parametrisierbare Validierungsregel lässt sich als Funktion höherer Ordnung repräsentieren. Diese nimmt die gewünschten Parameter entgegen und retourniert dafür eine Funktion mit den zuvor erwähnten Eigenschaften.

Das nachfolgende Beispiel zeigt eine solche Funktion, welche den Wertebereich einer Zahl prüft:

import {Control} from 'angular2/common';

export class RangeValidator {

    static create(min: number, max: number): Function {
        return (c: Control): any => {
            if (!c.value) return {};

            var value = Number(c.value);

            if (isNaN(value)) return {'range': true};
            if (value >= min && value <= max) return {};

            return {'range': true};
        };
    }

}

Attribut für parametrisierbare Validierungsregel

Um eine Validierungsregel als Attribut bereitzustellen, ist eine Direktive einzurichten. Das nachfolgende Beispiel demonstriert den Aufbau solch einer Direktive. Sie erhält für jeden Parameter der Validierungsfunktion ein Konstruktorargument. Diese Argumente beziehen ihre Werte aus den gewünschten Attributen. Zum Festlegen der Namen dieser Attribute kommt der Dekorator @Attribute zum Einsatz. Mit diesen Werten und der oben gezeigten Funktion erzeugt der Konstruktor die parametrisierte Validierungs-Funktion. Darüber hinaus implementiert die Direktive das Interface Validator und die davon vorgegebene Methode validate, welche an die Validierungs-Funktion delegiert.

Um festzulegen, auf welche Eingabefelder die Validierungsregel anzuwenden ist, erhält die Direktive einen Selektor. Demnach kommt die Direktive bei input-Elementen, die sowohl das Attribut min als auch das Attribut max aufweisen, zum Einsatz. Für diese Elemente bindet sich die Direktive selbst an das Token NG_VALIDATORS. Da jedes Feld mehrere Validierungsregeln aufweisen kann, kommt hierzu per Definition ein Multi-Binding zum Einsatz. Dies zeigt auch schön, wie Angular 2 intern deklarative Validierungen abarbeitet: Es zieht pro Feld per Dependency Injection alle an NG_VALIDATORS gebundenen Validatoren heran und bringt sie zum Ermitteln des Validierungsergebnisses zur Ausführung.

import {Directive, provide, Attribute} from 'angular2/core';
import {Control, NG_VALIDATORS, Validator} from 'angular2/common';

@Directive({
  selector: 'input[min][max]',
  providers: [provide(NG_VALIDATORS, {useExisting: RangeValidatorDirective, multi: true})]
})
export class RangeValidatorDirective implements Validator {

    validator: Function;

    constructor(@Attribute('min') min: number, @Attribute('max') max: number) {
        this.validator = RangeValidator.create(min, max);
    }

    validate(c: Control) {
        return this.validator(c);
    }

}

Validierungs-Attribut nutzen

Zum Anwenden der Validierungsregel ist die entwickelte Direktive bei der Komponente der Wahl zu registrieren:

@Component({
    templateUrl: 'app/flug-edit/flug-edit.html',
    directives: [RangeValidatorDirective]
})
export class FlugEdit  {
    [...]
}

Alternativ dazu kann die Anwendung die Direktive auch beim Bootstrapping global registrieren. Dazu verknüpft sie sie über ein Multi-Binding mit dem Token PLATFORM_DIRECTIVES:

import {PLATFORM_DIRECTIVES} from 'angular2/core';

[...]

var services = [
    [...]
    provide(PLATFORM_DIRECTIVES, {useValue: RangeValidatorDirective, multi: true})
];

bootstrap(App, services);

Danach kann die Direktive im Template der gewünschten Komponente genutzt werden. Dazu ist lediglich ein Element, das zum festgelegten Selektor passt, einzurichten. Im betrachteten Fall handelt es sich dabei um ein input-Element mit den Attributen min und max.

Um herauszufinden, ob die Validierungs funktioniert hat, nutzt das Template die Methode hasError des Controls. Dabei übergibt sie jenen Schlüssel, der im Fehlerbeschreibungsobjekt den Validierungsfehler repräsentiert. Im hier gezeigten Fall ist das der Schlüssel range.

<form #f="ngForm">
<div class="form-group">
    <label>Id: </label>
    <input [(ngModel)]="flug.id" ngControl="id" class="form-control" min="0" max="9999">
    <div *ngIf="f.controls.id?.hasError('range')">
        Wert muss zwischen 0 und 9999 liegen!
    </div>
</div>
</form>

[1] https://github.com/manfredsteyer/forms-sample-jm-2016-03