• 10. Juni 2016

Eigene Formular-Steuerelemente für Angular 2 schreiben

Update im Jänner 2017: Dieser Beitrag wurde für die finale API von Angular 2 aktualisiert.

Sollen eigene Angular 2-Komponenten mit deklarativen (template-driven) und imperativen Formularen zusammenspielen, ist das Interface ControlValueAccessor zu implementieren. Die von diesem Interface definierten Methoden erlauben ein Abgleichen der Komponente mit dem Objektgraphen, über den Angular 2 ein Formular beschreibt.

Solche Steuerelemente arbeiten - wie input-Elemente - mit ngModel bzw. ngControl zusammen. Zur Veranschaulichung nutzt das nachfolgende Beispiel eine benutzerdefinierte Komponente date-control, die sich an die Eigenschaft mit ngModel an die Eigenschaft date bindet:

<!-- Deklaratives (template-driven) Forms-Handling 
<date-control [(ngModel)]="date"></date-control>

Alternativ dazu kann sich solch eine Komponente jedoch auch über das imperative Forms-Handling an ein vordefiniertes Control-Objekt binden. Das nachfolgende Beispiel zeigt dies, indem es das date-control mit formControlName an das FormControl-Objekt mit dem Namen date bindet. Dieses Control-Objekt erwartet Angular in der über formGroup festgelegten FormGroup.

<!-- Imperatives Forms-Handling -->
<form [formGroup]="filter">	
    <date-control formControlName="date"></date-control>
    [...]
</form>

Die FormGroup sowie das FormControl sind dabei über die Komponente bereitzustellen:

@Component({
    selector: 'flight-search',  
    template: require('./flight-search.component.html'),
    directives: [DateControlComponent]
})
export class FlightSearchImpComponent {
    
    
    public filter: FormGroup;
    
    constructor(private fb: FormBuilder) {
            
            
        this.filter = fb.group({
           date: ['2016-05-01']
        });
	}
	
	[...]
}

Dieser Beitrag beschreibt die nötigen Schritte zur Implementierung von ControlValueAccessor. Das gesamte Beispiel findet sich hier.

ControlValueAccessor

Das von Angular 2 bereitgestellte Interface ControlValueAccessor bietet drei Methoden zum Abgleich des Zustands eines Steuerelement mit dem von Angular für das Formular eingerichteten Objektgraphen.

//
// From the Angular2-Sources
//
export interface ControlValueAccessor {
    writeValue(obj: any): void;
    registerOnChange(fn: any): void;
    registerOnTouched(fn: any): void;
}

Um einen Wert ins Steuerelement zu schreiben, nutzt Angular die Methode writeValue. Da Angular jedoch auch über Änderungen am Laufenden bleiben muss, registriert das SPA-Flagschiff mit registerOnChange und registerOnTouched jeweils einen Callback. Ersteren ruft das Steuerelement per Definition auf, wenn der Benutzer den Wert ändert. Letzteres signalisiert, dass das Feld zumindest den Fokus hatte.

ControlValueAccessor implementieren

Das nachfolgende Beispiel demonstriert die Realisierung des Interfaces ControlValueAccessor. Es handelt sich dabei um eine einfache Komponente zum Bearbeiten von Datumswerten. Die Methode splitDate nimmt ein Datum entgegen und zerlegt es in seine Bestandteile, die es anschließend im (hier nicht abgebildeten) Template zum Editieren anbietet. Den umgekehrten Weg beschreitet die Methode apply, indem sie diese einzelnen Bestandteile wieder zum einem Datum zusammenfügt.

import { Component } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';

@Component({
    selector: 'date-control',
    template: require('./date-control.component.html')
})
export class DateControlComponent 
                    implements ControlValueAccessor {
    
    day: number;
    month: number;
    year: number;
    hour: number;
    minute: number;
    
    constructor(private c: NgControl) {
        c.valueAccessor = this;
    }

    writeValue(value: any) {
        this.splitDate(value);
    }
    
    onChange = (_) => {};
    onTouched = () => {};
    
    registerOnChange(fn): void { this.onChange = fn; }
    registerOnTouched(fn): void { this.onTouched = fn; }

    
    splitDate(dateString) {
      var date = new Date(dateString); 
      
      this.day = date.getDate();
      this.month = date.getMonth() + 1;
      this.year = date.getFullYear();
      this.hour = date.getHours();
      this.minute = date.getMinutes();
    }
    
    apply() {
        
        var date = new Date();
        date.setDate(this.day);
        date.setMonth(this.month - 1);
        date.setFullYear(this.year);
        date.setHours(this.hour);
        date.setMinutes(this.minute);
        date.setSeconds(0);
        date.setMilliseconds(0);
        
        this.onChange(date.toISOString());
        this.onTouched();
    }
    
}

Damit diese Komponente mit dem Forms-Handling von Angular 2 zusammenspielen kann, implementiert sie das Interface ControlValueAccessor. Zusätzlich lässt sie sich die aktuelle Instanz von NgControl injizieren. Diese Instanz repräsentiert die Komponente im von Angular für ein Formular erzeugten Objektgraphen. Über die Eigenschaft valueAccessor gibt die Komponente bekannt, dass sie selbst als ihr eigener ControlValueAccessor fungiert.

Die Implementierung von writeValue nimmt einen neuen Wert vom Framework entgegen und delegiert ihn an splitDate. Die Implementierungen von registerOnChange und registerOnTouched nehmen hingegen den von Angular übergebenen Callback entgegen und hinterlegen diese in den Member-Variablen onChange bzw. onTouched.

Standardmäßig verweisen diese Member auf Funktionen, die keine Aufgaben erfüllen. Auf diese Weise ist gewährleistet, dass sie zu jedem Zeitpunkt auf eine gültige Funktion verweisen. Somit muss sie ein Aufrufer nicht gegen null bzw. undefined prüfen.

Nach dem Bearbeiten des Datums ruft das Template die Methode apply auf. Sie fügt die einzelnen Bestandteile des Datums zu einem Datum zusammen und übergibt es durch Aufruf von onChange an Angular. Zusätzlich bringt es der Vollständigkeit halber die Methode onTouched zur Ausführung.

 

 
Hier können Sie eine Anfrage für eine unverbindliche Schulung ode Beratung bzw. einen Workshop erstellen.
 
Unverbindliche Anfrage
 
 

Schulungen

Angular Schulung: Strukturierte Einführung

Lernen Sie in dieser interaktiven Schulung anhand einer Beispielanwendung den Einsatz von Angular für Ihre erfolgreichen Projekte kennen. Sie durchdringen die Hintergründe und bauen von der ersten Minute an auf Best Practices auf.

Details

Advanced Angular: Enterprise-Anwendungen und Architektur

In dieser Schulung erfahren Sie alles für die Entwicklung großer Anwendungen mit Angular: Mono-Repos, Micro-Apps, State Management, Performance und mehr

Details

Angular: Strukturierte Einführung

Seit der Ankündigung von Angular (2+) fragen sich Entwicklungs-Teams, welche Migrationspfade für AngularJS-1.x-Anwendungen vorliegen werden. Das im Lieferumfang von Angular enthaltene ngUpgrade bietet eine Antwort darauf. Es erlaubt einen Parallelbetrieb von AngularJS 1.x und Angular (2+) und stellt somit die Grundlage für eine schrittweise Migration dar.

Details

Progressive Web-Apps mit Angular

Progressive Web Apps bieten den Komfort nativer Anwendungen, indem sie auf moderne Browser APIs, wie Service Worker, setzen. Sie sind installierbar sowie offlinefähig und nutzen Hintergrundprozesse für Datensynchronisation und Push-Notifications. Diese Schulung zeigt anhand eines durchgehenden Beispiels was sich genau hinter diesem neuen Konzept verbirgt, wie solche Anwendungen mit Angular entwickelt werden und wie Sie in Ihren Projekten von den dahinterstehenden Ideen profitieren.

Details

Reaktive Architekturen mit Angular und Redux

Dieses interaktive Seminar vermittelt, wie Sie reaktive Anwendungen mit Angular entwickeln können.

Details

TypeScript

TypeScript gibt Ihnen alle Möglichkeiten der neuen JavaScript-Standards und zusätzlich ein statisches Typsystem, das dabei hilft, Fehler möglichst früh zu erkennen. Außerdem ist TypeScript die Grundlage für Angular. In diesem interaktiven Seminar lernen Sie diese mächtige Sprache anhand einer Fallstudie kennen.

Details

Weitere Schulungen ...