GEDOPLAN
Webprogrammierung

Angular Formulare – Formatter und Parser, Teil 2

Webprogrammierung
checklist 2077020 640 2

Ein großer Anteil in Businessanwendungen wird sicherlich den Formularen zufallen. Von der einfachen Eingabe eines Textes bis hin zu aufwendigen GUI-Komponenten haben sie doch alle eines gemeinsam: am Ende müssen die erfassten Daten im gewünschten Format in der gewünschten Qualität im Datenmodel landen. Den einzig richtigen Weg gibt es nicht, schauen wir uns also einige der möglichen Varianten an

checklist 2077020 640 2

Als einfaches Beispiel dient uns wie zuvor das Beispiel der Kundennummer Format: 6stellig numerisch mit dem Präfix: “C-“. Im ersten Teil haben wir für einen Validator gesorgt der dieses Format überprüft. In diesem Teil gehen wir einen Schritt weiter. Wir wollen dem Benutzer ein etwas toleranteres Eingabeverhalten bieten: Die Kundennummer darf auch nur aus 6 numerischen Werten bestehen. Angular sollte in diesem Fall natürlich dafür sorgen das unser Format ( C-XXXXXX ) trotzdem eingehalten wird.

Pragmatisch

Eine sehr einfache pragmatische Lösung ist es mittels Event-Listener die eingegebenen Werte anzupassen, falls benötigt. Dazu ist nicht viel nötig, außer eine entsprechende Methode innerhalb der Komponente-klasse

Template

 <input type="text" formControlName="customernumber1" 
         (blur)="formatNumber('customernumber1')" />

Komponente

  formatNumber(formControlName) {
    const control = this.form.controls[formControlName];
    if (control.value && control.valid && !/^C-.*?/.test(control.value)) {
      control.patchValue('C-' + control.value);
    }
  }

Erfüllt seinen Zweck und mag für einige Anwendungsfälle ausreichen. Diese Lösung ist aber kaum wiederverwendbar, kapselt es die Logik zur Formatierung doch in einer konkreten Komponente. Selbst, wenn wir dies in einen separaten Service auslagern bleibt der fade Beigeschmack das es hier eine elegantere Lösung gibt

Formatter als Direktive

Warum also die Logik nicht aus der Komponente herauslösen und das Ganze in eine separate Direktive verpacken

@Directive({
  selector: '[gedCustomernumberFormatter]'
})
export class CustomernumberFormatterDirective {

  constructor(private el: ElementRef, 
              private renderer: Renderer2,
              private control: NgControl) { }

  @HostListener('blur', ['$event.target.value'])
  onBlur(val: string) {
    if (this.control.valid && val && !/^C-.*/.test(val)) {
      this.renderer.setProperty(this.el.nativeElement, 'value', 'C-' + val);
    }
  }
}

Eine klassische Direktive. Wir registrieren uns per HostListener-Binding an das Blur-Event des Eingabefeldes und formatieren den übergebenen Wert. Kleines Gimmick: wir injizieren uns das zugrundeliegende FormControl, um die Formatierung der Eingabe nur dann vorzunehmen wenn die Validierung auch erfolgreich war

Verwendung

<input type="text" formControlName="customernumber2" gedCustomernumberFormatter />

Formatter als ValueAccessor

Mit den Lösungen oben haben wir für die meisten der Situationen das passende Mittel zu Hand. Eine kleine Anpassung unserer Anforderung führt aber zur Erforderniss einer Alternative: der Anwender soll unsere Präfix (“C-“), das später Teil des Datensatzes sein muss, nicht sehen . Allgemein ausgedrückt: der eingegeben / sichtbare Wert soll sich vom Wert der im Datenmodel vorliegt unterscheiden. Von einer pragmatischen Lösung wie oben innerhalb der konkreten Komponente, gibt es auch hier einen eleganten Weg: einen eigenen ControlValueAccessor der das Bindeglied zwischen Komponenten-Klasse und UI darstellt

@Directive({
  selector: '[gedCustomernumberFormatterAccessor]',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomernumberFormatterAccessorDirective),
      multi: true
    }
  ]
})
export class CustomernumberFormatterAccessorDirective implements ControlValueAccessor {

  constructor(private el: ElementRef, private renderer: Renderer2) { }

  updateValue: any;
  touched: any;

  writeValue(obj: any): void {
    obj = obj.replace('C-', '');
    this.renderer.setProperty(this.el.nativeElement, 'value', obj);
  }

  registerOnChange(fn: any): void {
    this.updateValue = fn;
  }

  @HostListener('blur', ['$event'])
  onInput(event: any) {
    this.touched();
    let val = event.target.value;

    if (!/^C-/.test(val)) {
      val = 'C-' + val;
    } else {
      this.renderer.setProperty(this.el.nativeElement, 'value', val.replace('C-', ''));
    }

    this.updateValue(val);
  }

  registerOnTouched(fn: any): void {
    this.touched = fn;
  }
}


Die spannenden Stellen Zusammengefasst:

  • providers-Array, wir registrieren unsere Komponente als NG_VALUE_ACCESSOR, ähnlich wie bei den Validatoren um Angular mitzuteilen, das es sich bei dieser Komponente um ein Control handelt
  • Interface ControlValueAccesor, mit den Methoden
    • writeValue, Model > View, hier entfernen wir das Präfix, falls hier initiale Daten von der FormControl übergeben werden
    • registerOnChange, Change-Methode registrieren die wir später bei Änderungen triggern werden (wird von Angular aufgerufen, wenn die Komponente der FormGroup hinzugefügt wird)
  • HostListener, wenn der Focus das Textfeld verlässt sorgen wir dafür, dass:
    • das “C-” Präfix zum übertragenden Wert hinzugefügt wird, falls nicht vorhanden
    • das “C-” Präfix im Textfeld entfernt wird, falls der Benutzer es eingeben hat (optional)
    • der geänderte Wert (Benutzereingabe + Präfix) an die vorher registrierte Change-Methode übergeben wird

Verwendung

<input type="text" formControlName="customernumber3" gedCustomernumberFormatterAccessor />
2019 11 21 11 13 10 nginput

Cool. Das erlaubt es uns (gekapselt und wiederverwendbar) Benutzereingaben bei der weiteren Verarbeitung zu formatieren, ohne das der Benutzer dies wissen / sehen muss.

Gibt es das auch bei GitHub? Klar. https://github.com/GEDOPLAN/ng-input

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Bitte füllen Sie dieses Feld aus.
Bitte füllen Sie dieses Feld aus.
Bitte gib eine gültige E-Mail-Adresse ein.
Sie müssen den Bedingungen zustimmen, um fortzufahren.

Autor

Diesen Artikel teilen

LinkedIn
Xing

Gibt es noch Fragen?

Fragen beantworten wir sehr gerne! Schreibe uns einfach per Kontaktformular.

Kurse

weitere Blogbeiträge

IT-Training - GEDOPLAN
Java SE

Is 1.0 equals 1.00?

If you use BigDecimal for floating point numbers, you may want to compare them by equals. But beware: BigDecimal.equals takes…
match 1899824 640
Jakarta EE (Java EE)

CDI 4.0: Events mit BeanManager feuern

CDI Events können mit Hilfe einer Event Source gefeuert werden, z. B. @Inject @Urgent Event<Message> urgentMessageEventSource; ... urgentMessageEventSource.fire(new Message(...)); Im…

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!