Sie sind hier: Weblog

Parser und Formatter in Angular 2

Foto ,
11.01.2016 19:00:00

Dieser Beitrag bezieht sich auf Angular 2 Beta 0. Bei künftigen Releases kann es zu Änderungen kommen.

AngularJS 1.x erlaubt mit seinem Parser-Konzept das Verarbeiten von Benutzereingaben, bevor ng-model sie über die Datenbindung ans Model zurückschreibt. Analog dazu können Formatter gebundene Daten aus dem Model formatieren, bevor sie in einem Eingabefeld aufscheinen.

In Angular 2 (Beta 0) gibt es solch ein Konzept nicht - zumindest nicht auf den ersten Blick. Allerdings findet man hier das Konzept des ValueAccessors. Dabei handelt es sich um Klassen, die für die Synchronisation zwischen Steuerelementen und dem Model zuständig sind. Da unterschiedliche Steuerelemente, wie Checkboxes oder Eingabefelder, hierbei unterschiedlich zu nutzen sind, gibt es auch verschiedene ValueAccessor-Implementierungen.

Für eigene Implementierungen bietet sich u. a. die Basis-Klasse DefaultValueAccessor an. Zum Zurückschreiben ins Model weist sie die Callbacks onChange und onTouched auf. Diese Callbacks sind nach Änderungen im jeweiligen Steuerelement oder beim Verlassen des jeweiligen Steuerelements anzustoßen. Daneben gibt es eine Methode writeValue, die einen Wert aus dem Model bekommt und ins Steuerelement schreibt.

Das nachfolgende Beispiel demonstriert, wie Entwicklungs-Teams damit die gebundenen Daten im Rahmen der Datenbindung modifizieren können. Es wandelt ein Datum, das im Model als ISO-String vorliegt, in ein deutsches Datum um und schreibt Modifikationen an diesem Datum in Form eines ISO-Strings ins Model zurück.

Um das Zurückschreiben ins Model zu beeinflussen, richtet das betrachtete Beispiel Event-Handler für das input- und das blur-Event des Host-Steuerelements ein. Beim Host-Steuerelement handelt es sich beispielsweise um ein Eingabefeld (<input>) oder eine Textarea. Diese definiert es über die Eigenschaft host des Directive-Dekorators. Der Event-Handler für input modifiziert die Eingaben durch Aufruf des zuvor besprochenen Callbacks onChange.

Zum Beeinflussen der im Steuerelement präsentierten Daten überschreibt das Beispiel die Methode writeValue. Sie kümmert sich um die Formatierung und delegiert anschließend an die Basis-Implementierung von writeValue, welche ins Steuerelement schreibt, weiter.

Über die Eigenschaft selector gibt die Implementierung bekannt, für welche Elemente sie zu nutzen ist. Der Wert input[date] adressiert dabei input-Elemente mit einem date-Attribut, beispielsweise <input date [(ng-model)]="datum">.

Damit Angular 2 die Direktive tatsächlich als ValueAccessor nutzt, ist ein Provider einzurichten, der sie ans Token NG_VALUE_ACCESSOR bindet. Zur Laufzeit ruft Angular 2 sämtliche Elemente, die an dieses Token gebunden wurden, ab und nutzt sie zur Datenbindung beim jeweiligen Element. Die beim Definieren des Providers hinterlegte Angabe multi: true sagt aus, dass mehrere Elemente an NG_VALUE_ACCESSOR gebunden sein dürfen. Die Indirektion über forwardRef löst das Henne-Ei-Problem, das durch die gegenseitige Referenzierung zwischen dem Provider und dem ValueAccessor entsteht.

import {Directive, Renderer, ElementRef, Self, forwardRef, Provider} from angular2/core;

import {NG_VALUE_ACCESSOR, DefaultValueAccessor } from angular2/common;
import {CONST_EXPR} from angular2/src/facade/lang;

const PROVIDER = CONST_EXPR(new Provider(
    NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => DateValueAccessor), multi: true}));

@Directive({
  selector:
      input[date],
  host: {(input): input($event.target.value), (blur): blur()},
  providers: [PROVIDER]
})
export class DateValueAccessor extends DefaultValueAccessor {

    constructor(_renderer: Renderer, _elementRef: ElementRef) {
       super(_renderer, _elementRef);
    }

    blur() {
        this.onTouched();
    }

    input(value) {

        // Write back to model   
        if (value) {
            value = value.split(/\./);
            value = value[2] + "-" + value[1] + "-" + value[0];
        }

        this.onChange(value);
    }

    writeValue(value: any): void {

        // Write to view
        if (value) {
            var date = new Date(value);
            value = date.getDate() + "." + (date.getMonth()+1) + "." + date.getFullYear(); 
        }

        super.writeValue(value);

    }

}