Angular Theming, Library-Komponenten

In folgendem Artikel: Angular Material Theming haben wir uns bereits angesehen wie wir eigene Themes für Angular Material schreiben. Auch haben wir gesehen wie wir die so definierten Farb-Paletten und Typografie-Elemente in unseren eigenen Komponenten einsetzen können. Das war ganz einfach, durch eine kluge Trennung der Style-Definitionen und einen Import des aktuell verwendeten Themes. Doch was wenn unsere Komponenten nicht Teil unseres Projektes sind, sondern in einem separaten Projekt / eigenen Komponente-Bibliothek ausgelagert sind? Hier haben wir ja keinen direkten Zugriff auf das aktuelle Theme, schließlich wird das ja erst durch die konkrete Anwendung festgelegt.

Unsere eigene Komponenten Bibliothek muss also analog zum Vorgehen bei Angular Materials durch entsprechende Mixin-Funktionen mit dem aktuellen Theme versorgt werden. Um das zu tun bedarf es einiger Vorarbeit innerhalb unserer Komponenten.

Zusätzlich zu der üblichen Komponenten-Style-Datei implementieren wir eine theme.scss für unsere Bibliothek. Diese Datei enthält alle Regeln mit Theme-spezifischen Eigenschaften für die in der Bibliothek enthaltenden Komponenten. Um diese später in den konkreten Anwendungen zu definieren benötigen wir entsprechende Mixin-Funktionen:

@import '~@angular/material/theming';

@mixin ng-lib-theme($theme) {
  $primary: map-get($theme, primary);
  $accent: map-get($theme, accent);

  lib-panel div.body {
    border-top-color: mat-color($primary, 400);
    border-bottom-color: mat-color($accent, 400);
  }
}

@mixin ng-lib-typography($config) {
  lib-panel div.body {
    font-size: mat-font-size($config, title);
  }
}

Wie zu sehen definieren wir jeweils eine eigene Mixin-Funktion für das Theme und Typografie die mit einem entsprechenden Parameter versorgt werden (Theme-Objekt bzw. Typografie-Konfig). Aus dem Theme können wir dann z.B. mittels „map-get“ die einzelnen Paletten extrahieren (primary, accent, warning) die dann wiederum dazu genutzt werden können um konkrete Farbwerte zu ermitteln. Diese werden dann wie bereits gesehen in einzelnen CSS-Regeln verwendet um Theme spezifische Ergänzungen zu den Komponente-Styles zu liefern.

Innerhalb er konkreten Anwendung ähnelt die Registrierung dann erschreckend der von Angular Materials:

@include ng-lib-theme($theme02);
@include ng-lib-typography($custom-typography);

Zugegeben mit „ein bisschen“ CSS ist es hier nicht getan. Auch um die Verwendung von SASS als CSS-Präprozessor kommen wir an dieser Stelle nicht mehr herum. Der ist aber ohnehin zu empfehlen, auch wenn man im eigenen Projekt die Möglichkeiten von SASS vielleicht gar nicht ausschöpft oder sogar nur Kernfunktionen wie die Definition von Variablen nutzt. Ob die Anbindung der eigenen Komponenten an das Material Theming, wie zu letzt gesehen, notwenig ist hängt sicher von der Anwendung selbst ab. Insbesondere bei internen Komponenten-Bibliotheken die keinerlei dynamischen Theming-Funktionalitäten benötigen erfüllt eine zentrale Library mit festgelegtem Look&Feel ( in unserem Beispiel wäre das die theme.scss und variables.scss) sicherlich auch seinen Zweck.

GitHub? Klaro.

maven vulnerability check

Maven ist eine tolle Erfindung. Abhängigkeiten und Versionen deklarieren und den Rest erledigt der Mechanismus ganz im Hintergrund. Im Laufe des Projekt-Lebens lassen sich neue Abhängigkeiten ganz einfach per zusätzlichem XML Eintrag hinzufügen; logisch das man dabei in aller Regel die neueste Version der Bibliothek nimmt die in der verwendeten Architektur möglich ist. Aber Moment… die bestehenden Abhängigkeiten, die teilweise schon Jahre alt sind, sollte man die vielleicht nicht auch mal anheben? Selbst wenn wir keine Features vermissen, kritische Fehler wollen wir doch in unseren Abhängigkeiten und damit in unserer Anwendung nicht dulden…

Eine regelmäßige Kontrolle der Versionen unserer Bibliotheken macht natürlich aus Sicherheitsaspekten Sinn. Dafür sollten wir uns natürlich etwas Hilfe holen, ein Beispiel dafür

OWASP Dependency Check

Eine Organisation die sich um die Analyse und vor allem Protokollierung von Sicherheitslücken in Software kümmert. Passend dazu werden Plugins für Ant, Maven und Jenkins geboten. Die Integration zum Beispiel in Maven ist relativ einfach:

<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>5.2.4</version>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>   

natürlich lässt sich hier noch eine ganze Menge mehr einstellen ( Doku ). Mit einem entsprechenden Maven-Aufruf

mvn org.owasp:dependency-check-maven:check

Generiert unser Plugin nun ein ansehnliches HTML-Protokoll, auf dessen Basis wir prüfen und entscheiden können, ob die Portierung unserer Bibliotheken notwendig ist:

Das passende Jenkins-Plugin lässt sich darüber hinaus auch mit entsprechenden Schwellwerten versehen um z.B. im NightlyBuild Fehler / Warnung auszugeben und den viel beschäftigten Entwickler auf Handlungsbedarf aufmerksam zu machen 😉

Angular Material Theming

Angular als Framework für die Entwicklung von anspruchsvollen Webanwendungen bringt alles mit was der Entwickler braucht. Alles? Nicht ganz. Ähnlich wie bei JSF werden wir in aller Regel zusätzliche ( optische ) Fremd-Komponenten integrieren. Neben PrimenNG (Primetek, Primefaces) kommt aus der Feder von Google selbst @angular/material . Basierend auf Googles Material Design erhalten wir eine ganze Reihe von Komponenten vom Button bis zur Datatable. Doch was wenn uns diese optisch nicht so ganz zusagen und wir eigene Farb- und Schrift-Gestaltungen einbringen wollen?

Schauen wir zuerst einmal wie Angular Material sich integrieren lässt. Dazu eilt uns, wie so oft, die Angular-CLI zur Hilfe, die uns fast die gesamte Arbeit abnimmt:

ng add @angular/material

Damit werden die benötigten Abhängigkeiten installiert und unser Basis-Style Datei ergänzt. Bei der Installation wählen wir unter anderem das Theme welches wir verwenden wollen. Hier sehen wir bereits das Google mit einigen Standard-Themes um die Ecke kommt, für die wir nichts mehr tun müssen. Alternativ wählen wir „custom“ um unser eigenes Theme zu definieren ( kann später auch problemlos ergänzt und geändert werden ).

Die Grundidee bei der Farbgestaltung von Googles Material basiert auf 3 Farben:

  • primary, Hauptfarbe
  • accent, Zweitfarbe
  • warn, Farbe für Fehler
import { MatButtonModule, MatBadgeModule } from '@angular/material';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
...
@NgModule({
...
  imports: [... MatButtonModule,MatBadgeModule]
})
export class AppModule {}
<button mat-raised-button color="primary" matBadge="8" matBadgePosition="after" matBadgeColor="accent">
  primary-accent
</button>

Anpassungen an diesen Farbwerten erfolgen nun über eine SCSS Datei. ( selbst dann wenn das Projekt selbst CSS als Styling-Variante verwendet, dann über die zusätzlich generierte Datei custom-theme.scss). Die Einbindung eines eigenen Theme erfolgt in 3 Schritten.

Farben

Alle diese Farben sind aber nicht einfache Farbwerte sondern sogenannte Farb-Paletten die sich zum einen dadurch auszeichnen das auch hellere und dunklere Farbe werte definiert sind, sondern auch Kontrast Farben ( z.B. der Text auf farbige Komponenten )

Eine Farbpalette definiert einen Farbraum der für einen der 3 Bereiche verwendet werden kann, die Definition erfolgt als SCSS Map mit den Abstufungen der Farbwerte, Farbvarianten für abgesetzte Texte ( A100, A200, A400, A700) und den Kontrast-Werten:

$palette01: (
  50: #ebf0f4,
  100: #ccd9e3,
  200: #aac0d0,
  300: #88a6bd,
  400: #6f93ae,
  500: #5580a0,
  600: #4e7898,
  700: #446d8e,
  800: #3b6384,
  900: #2a5073,
  A100: #b9dcff,
  A200: #86c3ff,
  A400: #53a9ff,
  A700: #3a9cff,
  contrast: (
    50: #000000,
    100: #000000,
    200: #000000,
    300: #000000,
    400: #000000,
    500: #ffffff,
    600: #ffffff,
    700: #ffffff,
    800: #ffffff,
    900: #ffffff,
    A100: #000000,
    A200: #000000,
    A400: #000000,
    A700: #000000
  )
);

Tipp: eine ganze nette Seite um solche Definitionen zu generierten ist: http://mcg.mbitson.com/

Farbpaletten

Über Mixin-Funktionen die Angular uns zur Verfügung stellt, können wir nun entsprechende Paletten generierten und diese registrieren

@import '~@angular/material/theming';
@include mat-core();


$theme02-primary: mat-palette($palette01);
$theme02-accent: mat-palette($palette02);
$theme02-warn: mat-palette($palette03);

$theme02: mat-light-theme($theme02-primary, $theme02-accent, $theme02-warn);
@include angular-material-theme($theme02);

Die Methode mat-palette hat optional 3 zusätzliche Parameter um aus der übergebenen Palette (über den Key, z.B. 200 ) die Default-Werte für primary,lighter,darker zu selektieren. Neben der oben verwendeten Funktion mat-light-theme existiert auch mat-dark-theme, um ein dunkles Layout zu erzeugen.

Neben der Übernahme des Themes für alle Komponenten wie oben zu sehen existiert auch die Möglichkeit Themes für einzelne Material-Komponenten zu definieren:

@include mat-badge-theme($theme03);

Komponenten

Bei der Verwendung der Material-Komponenten können wir die Farben nun durch entsprechende Attribute definieren wie bereits oben zu sehen. Wollen wir in unseren eigenen Komponenten auf die Farben der Paletten zugreifen erlauben uns entsprechende Mixin-Funktionen die zu tun

Dazu bietet es sich an folgende Trennung vorzunehmen:

  • theme.scss
    • Defitinion von Farben
    • Definition von Paletten
    • Definition von Theme
  • style.scss ( root – Style )
    • import der theme.css
    • Registrierung als Theme

style.scss

@import 'styles_theme.scss';
@include mat-core();
@include angular-material-theme($theme02);

component.scss

@import '../styles_theme.scss';

:host {
  background-color: mat-color($theme02-primary);
}

Tipp: durch folgenden Eintrag in der angular.json unter architect/build/options können wir die relative Angabe der Theme-SCSS durch eine absolute Variante ersetzen ( @import ’styles_theme.scss‘ )

            "styles": ...
            "stylePreprocessorOptions": {
              "includePaths": ["src/theme"]
            },
            "scripts": ...

That’s it. Zugegeben „einfach ein paar CSS-Klassen überschreiben“ mag auf den ersten Blick einfacher erscheinen als hier ein eigenes Theme zu erstellen und vor allem mittels der Funktionen zu registrieren. Aber auf diesem Wege haben wir ein valides Layout welches in allen Kombinationen auch gut lesbar ist.

Github….

Angular – im Griff – Compodoc

Angular Anwendungen können sehr schnell sehr groß werden, wollen wir doch möglichst kleine gut wartbare Bestandsteile entwickeln. Also füllt sich unser „Koffer“ mit allerhand Komponenten, Services und Modulen die am Ende des Tages irgendwie noch in den Griff zu bekommen sein sollen. Eine gute Struktur und vielleicht ein passendes Tool können da helfen.

In einer früheren Serie haben wir schon mal einen Blick auf die Möglichkeiten geworfen, die uns der Angular Workspace bietet (Angular strukturiert) . Ein entsprechender Workspace ist schnell dank Angular-CLI generiert ( ng new –create-application false ). Im folgenden kommen dazu:

  • zwei Libraries ( ng g library )
    • common-service
    • common-ui
  • zwei Anwendungen ( ng g application)
    • Hello01
    • Hello02

Wollen wir nun Komponenten, Services, Direktive etc. generieren erlaubt uns die Angular-CLI die entsprechenden Befehle mit der Option –project anzugeben um das Projekt zu definieren. Alternativ reicht es auch die Befehle innerhalb der Projekte auszuführen.

Libraries

Unsere beiden Libraries sind relativ unspektakulär, die Auslagerung in eine eigene Library würde es uns später erlauben diese auch separat, als NPM-Pakete zu verpacken. Neben der direkten Verwendung über den TypeScript Import (für zum Beispiel einfache Model-Klassen oder statische Funktionen) haben wir für beide Libraries jeweils ein Angular-Modul ausgebildet (ng g module). Dabei nicht vergessen: die Komponenten müssen im Modul im Export-Array registriert werden.

Applications

Unsere beiden Anwendungen importieren nun die Bibliotheken auf Basis der Module nach Bedarf

import { CommonUIModule } from 'projects/common-ui/common-ui/common-ui.module';

@NgModule({
  ....
  imports: [
    BrowserModule,
    AppRoutingModule,
    CommonUIModule
  ]
})
export class AppModule { }

… oder führen noch weitere Module (ng g module) hinzu die lazy über das Routing geladen werden (man beachte die neue Syntax ab Angular 7)

const routes: Routes = [
  {
    path: 'detail', 
    loadChildren: () => import('./modules/detail01/detail.module').then(m => m.DetailModule)
  },
  {
    path: 'detail02', 
    loadChildren: () => import('./modules/detail02/detail.module').then(m => m.DetailModule02)
  },
  {
    path: 'detail03', component: Detail03HomeComponent
  }
];

Wir sehen hier schon das wir selbst bei dieser sehr überschaubaren Struktur bereits die Fäden in der Hand behalten müssen. Insbesondere für Projekt-Fremde oder neue Kollegen die in einer gewachsenen Anwendung einsteigen wollen bereitet diese (durchaus sinnvolle) Struktur erst mal Schwierigkeiten.

Compodoc

https://compodoc.app/

Ein einfaches Tool, welches auf Basis unseres Projektes eine Dokumentation erzeugt, die sehr hilfreich ist um die Bestandteile der Angular Anwendung im Großen ganzen im Auge zu behalten. So generiert das Tool eine HTML Dokumentation in der neben einer visuellen Zusammenfassung der Verknüpfungen zwischen Komponenten, Services und Module auch die Suche nach bestimmten Anwendungsteilen möglich ist. Eine solche Übersicht die mittels: npx compodoc -d docs -p ./tsconfig.json erzeugt wird, steht dann als HTML-Datei zur Verfügung und könnte so aussehen:

Hier das Ganez als:

GEDOPLAN wünscht frohe Weihnachten

Live.

bei GitHub.

Ein bisschen Nachsicht bitte! Anwendungsstabilität mit MicroProfile Fault Tolerance

MicroProfile (https://microprofile.io/) enthält u. a. den Baustein MP Fault Tolerance, mit dem auf sehr einfache Weise die bekannten Stability Patterns in JEE/MicroProfile-Anwendungen eingebaut werden können.

Bei verteilten Anwendungen (Microservices) – aber nicht nur dort – können Fehler durch nicht erreichbare oder nicht funktionierende Anwendungsteile entstehen. Soll dann nicht wie bei einem Dominoeffekt die Gesamtanwendung betroffen sein, muss der aufrufende Teil resilient sein, d. h. durch Wiederholungen oder Ersatzaufrufe zu einem verträglichen Ergebnis kommen.

MP Fault Tolerance enthält Interzeptoren mit den im Folgenden beschriebenen Bindings, mit denen im Fehlerfall gewünschte Ausweichaktionen initiiert werden können.

Nochmals probieren

@Retry führt beim Auftauchen einer Exception zum wiederholten Aufruf der Methode:

@Retry(maxRetries = 4)
public int doSomethingWithRetry() {

Mit diversen Parametern von @Retry kann bestimmt werden, welche Exceptions die Wiederholungen auslösen sollen, wie lange oder wie oft es versucht werden soll und welche Wartezeit zwischen den Wiederholungen eingehalten werden soll.

Programmierte Ungeduld

Mit @Timeout kann eine Obergrenze für die Ausführungszeit einer Methode angegeben werden:

@Timeout(1000)
public int doSomethingWithTimeout() {

Rückstellende Sicherung

Circuit Breaker ist ein Pattern, das bei zu häufigen Fehlern (=Exceptions) „den Schalter öffnet“, d. h. die betroffene Methode für eine gewisse Zeit nicht mehr aufruft und statt dessen eine dementsprechende Exception wirft. Nach Ablauf der Wartezeit wird der Schalter probeweise wieder geschlossen.

MP Fault Tolerance bietet dazu @CircuitBreaker an:

@CircuitBreaker(failureRatio = 0.25, requestVolumeThreshold = 10)
public int doSomethingWithCircuitBreaker() {

Mit Parametern kann bestimmt werden, was „zu häufig“ sein soll und wann der Schalter nach Öffnung wieder geschlossen wird.

Ersatzhandlung

Sind Fehler (=Exceptions) für den Aufrufer nicht OK, kann mit @Fallback eine Ersatzaktion definiert werden:

@Fallback(fallbackMethod = "return42")
public int doSomethingWithFallback() {
  ...
}

private int return42() {
  return 42;

Plattformen

MicroProfile Fault Tolerance wird u. a. von folgenden JEE-Servern unterstützt:

  • OpenLiberty (mit aktiviertem Feature mpFaultTolerance-2.0).
  • Payara.

WildFly wird MP Fault Tolerance voraussichtlich ab der Version 19 unterstützen

Die genannten Annotationen befinden sich bspw. in der Maven-Dependency org.eclipse.microprofile.fault-tolerance:microprofile-fault-tolerance-api (derzeit in Version 2.0).

Demo

In https://github.com/GEDOPLAN/microprofile-demo/tree/master/microprofile-fault-tolerance finden Sie ein Beispielprojekt mit einem Service, der die oben gezeigten Annotationen enthält und über Rest Endpoints aufgerufen werden kann. Die Anwendung kann als WAR-File auf einen der genannten Server deployt werden. Alternativ können über vorkonfigurierte Maven-Profile Docker-Images zum direkten Ausprobieren erstellt werden.

Bis bald – vielleicht auch in einem unserer Trainings in Berlin, Frankfurt, Bielefeld, Köln oder bei Ihnen!

Schauen Sie sich doch mal unseren neuen Kurs zum Thema an: JEE und MicroProfile mit Quarkus

Angular Formulare – Formatter und Parser, Teil 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

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 />

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