Angular, HTTP Error Handler

Eine Rest-Schnittstelle über den von Angular bereitgestellten HTTP-Service an zu binden ist nicht schwer. Dank Observables ist auch die Fehlerbehandlung kein großer Aufwand. Trotzdem muss diese Behandlung für jede Kommunikation deklariert werden was zu lästigem Schreibaufwand führt und die Anwendung auch unübersichtlich macht. Wie sieht eine Möglichkeit aus, generisch auf HTTP Fehler zu reagieren, ohne diese Behandlung bei jedem Aufruf zu implementieren? Werfen wir einen Blick darauf.

app

Um zum Beispiel mit Rest-Schnittstellen zu kommunizieren verwenden wir in Angular den bereitgestellten HTTP Service der uns diverse Methoden zur Verfügung stellt die verschiedenen Request-Typen zu verwenden. Ein einfache Beispiel und dessen Verwendung könnte so aussehen:

jsonplace-holder.service.ts

@Injectable()
export class JSONPlaceHolderService {

  constructor(private http: Http, @Inject(SERVICE_BASE_URL) private baseurl:string) { }

  ladeUserDaten(userId: number): Observable<any> {
    return this.http.get( this.baseurl + 'users/' + userId).map(r => r.json());
  }

}

app.component.ts

  ladeDaten() {
    this.service.ladeUserDaten(5).subscribe(
      u => this.userDaten = u,
      error => console.log('Fehler: ' + error)
   );
  }

(die Fehlerbehandlung durch eine Konsolen-Ausgabe zu implementieren ist natürlich keine gute Idee. In aller Regel werden wir einen zentralen Service verwenden der sich darum kümmert das die Ausgaben an den Benutzer weiter gegeben werden)

Die Behandlung der Fehler, egal in welcher Form sie nun implementiert ist, müsste nun bei jedem Methodenaufruf verwendet werden. Selbst dann wenn wir lediglich eine ganze allgmeine Meldung ausgeben wollen.

Die eigene ‚http‘ Implementierung

Der Titel klingt umfassender als es wirklich ist. Eigentlich erweitern wir den bestehenden HTTP-Service lediglich um eine einheitliche Fehlerbehandlung.

Dazu überschreiben wir die Request-Methode, leiten den eigentlichen Aufruf an die originale Implementierung weiter und registrieren eine Methode („catch“) die sich um unsere Fehlerbehandlung kümmern soll. In diesem Fall leitet der HTTP-Interceptor diese Fehlermeldung an einen separaten Service weiter um den User diese Information an zu zeigen.

jsonplace-holder.service.ts

// operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/map';

@Injectable()
export class HttpInterceptor extends Http {

    constructor(backend: XHRBackend,options: RequestOptions, private errorService:ErrorHandleService) {
        super(backend, options)
    }

    public request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
        return super.request(url, options)
            .catch(this.handleError)
    }

    public handleError = (error: Response) => {
        this.errorService.addError(error.toString());
        return Observable.throw(error)
    }
}

Soweit so gut. Nun müssen wir lediglich dafür Sorge tragen das bei der Injizierung des HTTP-Services (wie oben zu sehen) unsere eigene Implementierung herangezogen wird und nicht die von Angular selbst. Dazu implementieren wir einen entsprechenden Provider in unserem Modul. Dieser Provider ist hier im Beispiel eine Factory-Methode, damit wir andere Services die ebenfalls per Depedency Injektion bereit gestellt werden an unsere Implementierung übergeben können:

app.module.ts

  providers: [
   ...
    ErrorHandleService,
    {
      provide: Http,
      deps: [XHRBackend, RequestOptions, ErrorHandleService],
      useFactory: (backend: XHRBackend, defaultOptions: RequestOptions, service: ErrorHandleService) => new HttpInterceptor(backend, defaultOptions, service)
    }
  ],

Nachricht an den User

Zur Vollständigkeit noch eine kurze Zusammenfassung wie die Benachrichtigung an den Benutzer erfolgt. Wie oben zu sehen verwenden wir einen Service der die Fehler entgegen nimmt. Dieser Service ist sehr überschaubar und hält lediglich ein „Subject“ (rxjs-Packet) bereit an dem sich andere Komponenten registrieren können. In unserem Mini-Beispiel erfolgt die Registrierung direkt in der Hauptkomponenten die bei neuen Nachrichten diese in einer Variablen ablegt, auf dessen Basis eine Meldung im Frontend angezeigt wird. Zusätzlich blenden wir die entsprechenden Fehlermeldungen noch nach einigen Sekunden aus.

error-handler.service.ts

@Injectable()
export class ErrorHandleService {

  public messages = new Subject<string>();

  addError(error: string) {
    this.messages.next(error);
    throw error;
  }
}

app.component.ts

    errorService.messages.subscribe(e => {
      this.globalErrorMessage = e;
      setTimeout(() => this.globalErrorMessage = null, 5000);
    });

app.component.html

<div *ngIf="globalErrorMessage">
<div class="alert alert-danger">
            {{globalErrorMessage}}</div>
</div>

 

Wie immer. Alles. Live. In Farbe. Bei Github

Advertisements

Angular, Validatoren

Formulare sind der Kern vieler Businessanwendungen. Neben der Prüfung innerhalb des Backends ist es in aller Regel auch eine Anforderung den Benutzer bereits bei der Eingabe auf eventuelle Fehler hinzuweisen. Angular bietet hierfür ein Konzept, das auf Direktiven basiert.

Bildschirmfoto vom 2017-06-08 13:03:43

Die von Angular mitgelieferten Validatoren sind überschaubar:

  • required, Pflichtfeld
  • minlength, Mindestlänge
  • maxlength, Maximallänge
  • pattern, Regular-Expression

Die Verwendung dieser Validatoren ist denkbar einfach. Nachdem das „FormsModule“ (import { FormsModule } from ‚@angular/forms‘;) in unser Modul importiert wurde, können diese Validatoren mittels Direktive an unseren Eingabefeldern registriert werden. Der Zugriff auf eventuelle Fehler erfolgt über den Zugriff mittels Templatevariablen (’ngForm‘ für Formulare, ’ngControlGroup‘ für Gruppen, ’ngModel‘ für Eingabefelder) und z.B. mit der Verwendung der „valid“ Eigenschaft.

<form #form="ngForm" (ngSubmit)="submit()" novalidate>
    <span *ngIf="!namefield.valid"> Pflichtfeld </span>
    <input name="name" type="text" class="form-control" required ngModel #namefield="ngModel">
    <button type="submit" [disabled]="!form.valid">submit</button>
</form>

Eigene Validatoren

Hierzu wird eine Direktive erstellt die mit Hilfe eine so genannten „Multi Providers“ den bestehenden Validatoren (NG_VALIDATORS) hinzugefügt wird (multi=true). Die eigentliche Implementierung erfolgt in der „validate“ Methode, die entweder null zurück liefert, wenn kein Fehlerfall besteht oder ein Objekt, welches weitere Informationen zur Anzeige bereit stellen kann. Neben einzelnen Feldern können so auch Felderübergreifende Prüfungen implementiert werden. Anstatt die Direktive auf einem einzelnen Feld ein zu setzen wird dann im Template eine FromGroup verwendet die dem Validator später übergeben wird. Hier das Beispiel eines solchen Validators der über zwei Felder hinweg prüft ob ein gültiger numerischer Bereich angegeben wurde. Hier sehen wir auch wie unser Validator mit Parametern konfiguriert werden kann, in diesem Fall benötigt er die Pfade zu den Form-Controls um diese aus der FormGroup zu erhalten.

app.component.html

<div ngModelGroup #range="ngModelGroup" appRangeValidator von-pfad="lvon" bis-pfad="lbis">
    <input name="lvon" type="text" ngModel>
    <input name="lbis" type="text" ngModel></div>
{{range?.errors | json}}

range-validator.directive.ts

import { Directive, Input } from '@angular/core';
import { NG_VALIDATORS, FormGroup } from '@angular/forms';

@Directive({
  selector: '[appRangeValidator]',
  providers: [
    {
      provide: NG_VALIDATORS, useExisting: RangeValidatorDirective, multi: true
    }
  ]
})
export class RangeValidatorDirective {

  @Input('von-pfad')
  path1: string;

  @Input('bis-pfad')
  path2: string;

  validate(element: FormGroup) {
    const [val1, val2] = [element.get(this.path1), element.get(this.path2)];

    if ([val1, val2].every(e => e && e.value && e.value !== '')) {
      if ([val1.value, val2.value].some(e => !/^\d+$/.test(e))) {
        return { 'invalid-error': { valid: false, message: 'Ungültige Eingabe' } };
      } else if (Number.parseInt(val1.value) > Number.parseInt(val2.value)) {
        return { 'range-error': { valid: false, message: 'Erste Zahl muß kleiner sein als die Zweite' } };
      }
    }

    return null;
  }

}

Asyncrone Validatoren

Neben dem gerade gezeigtem syncronen Validatore bietet Angular auch eine asycrone Möglichkeit Prüfungen durch zu führen, wenn die zu prüfenden Werte z.B. erst an eine Rest-Schnittstelle übertragen werden sollen. Der grundsätzliche Aufbau bleibt der gleiche, wir verwenden lediglich den Multi Provider „NG_ASYNC_VALIDATORS“ bei der Registrierung und unsere „validate“ Methode muss dann ein Promise oder Observable zurück liefern, das wiederrum „null“ oder ein Objekt mit den Fehler informationen enthält

async-user-validator.direvtive.ts

import { Directive } from '@angular/core';
import { NG_ASYNC_VALIDATORS, AbstractControl } from '@angular/forms';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';

@Directive({
  selector: '[appAsyncUserValidator]',
  providers: [
    { provide: NG_ASYNC_VALIDATORS, useExisting: AsyncUserValidatorDirective, multi: true }
  ]
})
export class AsyncUserValidatorDirective {

  constructor(private http: Http) { }

  validate(element: AbstractControl) {
    if (element && element.value && element.value !== '') {
      return this.http
        .get('https://jsonplaceholder.typicode.com/users')
        .map(r => r.json())
        .first()
        .map((e: any[]) => e.some(ele => {
          return ele.id == element.value
        }))
        .map(e => e ? null : { 'usernot': 'Benutzer existiert nicht' });
    }

    return Observable.from([null]);
  }
}

Der Validator fragt eine Rest-Schnittstelle nach Userdaten ab die als Rückgabe ein Array mit Usern liefert (für diesen Fall zugegeben ein schlechtes Schnittstellen-Design). Wir wandeln die Response hier in ein JSON-Objekt um, lassen uns das erste Elemente ( das Array ) geben, prüfen ob mindestens einer der User die übergebene ID besitzt und basierend auf diesem true/false Ergebnis erzeugen wir bei Bedarf einen entsprechenden Fehler.

(In einer echten Anwendung gehört die Kommunikation mit der Rest-Schnittstelle selbstverständlich in einen separaten Service!)

Zugegeben die Validatoren die Angular mitliefert sind überschaubar. Aber dank Bordmittel sind individuelle Validatoren schnell implementiert und auf die eigenen Bedürfnisse anpassbar.

Wie immer. Alles. Live. In Farbe. Bei Github

Angular, wiederverwendbare Komponenten Teil 2/2

Im ersten Teil haben wir einen Blick auf die Möglichkeiten geworfen die Angular uns mittels ngContent, ngTemplateOutlet und ngTemplateRef bietet. Im zweiten Teil dreht sich alles um Komponenten die ganz dynamische ihr Template anpassen.

ComponentFactory

In diesem Beispiel werfen wir einen Blick auf eine Komponente die bei ihrer Verwendung dynamisch weitere Komponenten erzeugen und sie dem DOM Tree hinzufügt. Das Komponenten-Template ist erwartungsgemäß überschaubar:

view-container-ref.compoent.html

<div #content></div>

Innerhalb unseres Komponenten-Templates definieren wir lediglich eine Stelle an der später unsere dynamischen Komponenten eingefügt werden sollen. Spannender ist in diesem Fall der Controller:

view-container-ref.component.ts

  @ViewChild('content', { read: ViewContainerRef })
  content: ViewContainerRef;

  boxFactory: ComponentFactory<BoxWithDefaultComponent>

  constructor(r: ComponentFactoryResolver) {
    this.boxFactory = r.resolveComponentFactory(BoxWithDefaultComponent);
  }

  ngAfterViewInit() {
    let box1 = this.content.createComponent(this.boxFactory);
    box1.instance.title = "Dynamic (1)";

    let box2 = this.content.createComponent(this.boxFactory);
    box2.instance.title = "Dynamic (2)";
  }

Im ersten Schritt verwenden wir den alt bekannten @ViewChild Decorator um Zugriff auf ein Element unseres Templates zu erhalten. Anders als vielleicht erwartet müssen wir jedoch zusätzlich angeben welche Art von Referenz wir an dieser Stelle verwenden möchten. Würden wir die read Property weglassen, würde Angular in diesem Fall dafür sorgen das wir das DIV-Element injiziert bekommen. Um später jedoch dynamische Inhalte hinzufügen zu können benötigen wir eine ViewContainerRef. Um dies Angular deutlich zu machen verwenden wir als zweiten Parameter des ViewChild-Decorator eine entsprechende read-Property

Für die Erzeugung von Komponenten benötigen wir nun eine entsprechende Factory. Um die korrekte Factory für unsere gewünschte Komponente zu erhalten lassen wir uns im Konstruktor eine Instanz vom Typ  ComponentFactoryResolver geben, mit dessen Methode resolveComponentFactory wir eine Factory-Implementierung erhaltenw welche den übergebenen Komponente-Typ erzeugen kann.

Nun haben wir zum einen die Referenz auf die Stelle in unserem Template an dem wir Inhalte einfügen wollen und eine Factory die unsere konkrete Komponente erzeugen kann. In der Methode ngAfterViewInit Methode fügen wir so über die ViewContainerRef und der Methode createComponent(FACTORY) neue Komponenten zu unserem Template hinzu. Über die Property instance des Rückgabewertes erhalten wie dann die erzeugte Komponente über dessen Property wir die Komponente mit Werten versorgen können.

Ein kleiner Schritt ist noch nötig: wir verwenden an dieser Stelle nicht direkt die Komponente BoxWithDefaultComponent damit der Angular Compiler aus Optimierungsgründe diese Komponente nicht aus dem Bundle wirft, benötigen wir noch einen Eintrag in der app.module.ts :

@NgModule({
 ...
  entryComponents: [BoxWithDefaultComponent],
 ...
})

createEmbeddedView

Neben dem Hinzufügen von „einfachen“ Komponenten bietet uns die ViewContainerRef auch die Möglichkeit dynamisch Templates zu verwenden. In diesem Beispiel Verwenden wir eine Direktive um eine eigene Iteration von Elementen zu implementieren:

news-repeat.directive.ts

@Directive({
  selector: '[appNewsRepeat]'
})
export class NewsRepeatDirective {

  @Input('appNewsRepeat')
  news: string[];

  @ContentChild(TemplateRef)
  tmp: TemplateRef<any>;

 constructor(private view: ViewContainerRef) { }

  ngOnInit() {
    this.news.forEach(text => {
      let title = text.substr(0, 20) + '...'
      let dynamicView = this.view.createEmbeddedView(this.tmp, {
        params: {
          title,
          text
        }
      });

    });
  }
}

Unsere einfache Direktive soll eine Liste von News-Beiträgen die sie erhält aufbereiten und als Liste darstellen. Anders als bisher lassen wir uns die ViewContainerRef per Konstruktor geben (unsere Direktive hat ja kein eigenes Template in dem wir eine Stelle „markieren“ können, also lassen wir uns damit die Referenz auf das Element geben an dem unsere Direktive verwendet wird). Es folgt die Injizierung der Referenz auf das vom Verwender bereit gestellte Template mittels ContetnChild.

Die eigentliche spannende Aufgabe übernimmt dann die ngOnInit Methode, die auf Basis der übergebenen Texte einen Titel extrahiert und dann für jedes Text-Element mittels createEmbeddedView ein Template hinzufügt. Um dem Anwender in seinem bereitgestellten Template den Zugriff auf das aktuelle Iterations-Objekt zu geben , definieren wir ein Übergabeobjekt params das wir für jede Iteration mit den entsprechenden Werten befüllen. Diese Werte sind nun im Template des Verwenders erreichbar:

Verwendung

<div [appNewsRepeat]="news">
  <ng-template let-entry="params">
    <div>
      <div>
        <h1>{{entry.title}}</h1>
      </div>
      <hr/>
      <div>{{entry.text}}</div>
    </div>
  </ng-template>
</div>

Wir übergeben an unsere Direktive die News-Beiträge mittels [appNewsRepeat]=“news“  (String-Array) und definieren schließlich ein Template mit einer Variable let-entry um Zugriff auf das Übergabeobjekt =“params“ zu bekommen, um damit dann unser individuelles Template zu erstellen.

Der zweite Teil hat gezeigt das auch sehr dynamische Komponenten relativ leicht selber zu erstellen sind. Angular bietet hier diverse Möglichkeiten und schreibt WIEDERVERWENDBARKEIT groß.

Wie immer. Live. In Farbe:

github.com/GEDOPLAN

Angular, wiederverwendbare Komponenten Teil 1/2

Komponenten sind das zentrale Konzept im Universum von Angular. Eine Komponente zu schreiben ist hier nicht schwer, die Herausforderung besteht in aller Regel darin Komponenten zu schaffen die möglichst viel Funktionalität so  kapseln das sie anderer Stelle wieder verwendet werde kann, dem „Nutzer“ aber den Freiraum lässt je nach Situation Anpassungen vornehmen zu können. Dieser Beitrag beschäftigt sich mit all den Möglichkeiten die Angular in diesem Kontext bietet.

ng-Content

Eine der leichteren Anforderungen ist es einen Bereich innerhalb der eigenen Komponente an zu bieten, in dem der Verwender eigene Inhalte platzieren kann. Ein typisches Beispiel wäre hier eine Layout-Komponete die einen bestimmten Rahmen vorgibt. Hierfür bietet Angular „ng-content“:

box-component.html

<div> <ng-content></ng-content></div>
<div> <ng-content select="[appBoxHeader]"></ng-content></div>
<div> <ng-content select=".footer"></ng-content></div>

In diesem Beispiel bietet die Komponente 3 Stellen an denen der Verwendet beliebige Inhalt einfügen kann. Die eindeutig Identifikation der Stellen wird durch Selektoren bewerkstelligt. In diesem Beispiel verwenden wir (von oben nach unten) eine Default-Stelle, eine Selektion über eine Direktive und eine Selektion über eine CSS-Klasse.

Die Verwendung dazu könnte wie folgt aussehen:

insert-children.component.html

  <app-box>
    <span>Hello Nr. 1</span>
    <span appBoxHeader>Hello Nr. 2</span>
    <span class="footer">Hello Nr. 3</span>
  </app-box>

ngTemplateOutlet

Einen Schritt weiter geht die Verwendung des ngTemplateOutlet. Hier können wir dem Anwender, auf Basis von HTML5 Templates, die optionale Möglichkeit geben bestimmte Teile unserer Komponente aus zu tauschen. Im folgenden Beispiel geht es darum eine sehr einfache Layout Komponente an zu bieten, die OutOfTheBox verwendet werden kann. Zusätzlich wollen wir dem Verwendet aber die Möglichkeit geben bei Bedarf bestimmte Teile mit eigenen Inhalten aus zu tauschen.

box-with-default.component.html

<div class="footer">
    <ng-template [ngTemplateOutlet]="footerTemplate"></ng-template>

© by GEDOPLAN, Dominik Mathmann</div>

Im Footer-Bereich unserer Komponente wollen wir eine standardisierte Copyright Information anzeigen, falls der Verwender keine eigenen Inhalte anbietet. In unserer Komponente sehen  wir dafür einen Bereich mittels „ng-template“ vor. Falls dieser Bereich vom Benutzer nicht mit „Leben gefüllt“ wird zeigen wir einen Standard-Text an. Sollte der Verwender ein entsprechendes Template übergeben sorgt die Direktive ngTemplateOutlet dafür das diese an der Stelle angezeigt wird. Die Referenz auf das individuelle Template (footerTemplate) lassen wir uns zu diesem Zweck in den Controller unserer Komponente injizieren:

box-with-default.component.ts

  @ContentChild("footer")
  footerTemplate: TemplateRef<any>;

Verwendung:

  <app-box-with-default title="Hello-Box">
       ...
       <ng-template #footer>
        <i>...my special footer...</i>
      </ng-template>
  </app-box-with-default>

Solche dynamischen Template-Bereiche können natürlich mehrfach in unserer Komponente vorkommen. Die Verknüpfung zwischen der Position innerhalb unserer Komponente und dem vom Verwender bereitgestellten Template geschieht über die Verwendung einer übereinstimmenden Template Variable (#footer > @ContentChild („footer“))

ngTemplateRef

Neben dem Austausch von festgelegten Template-Bereichen über TemplateRef gibt es auch Anwendungsfälle in denen wir innerhalb unserer Komponente eine Iteration durchführen wollen. Hier ist eine logische Anforderung, dass wir das Template der Iteration übergeben wollen, natürlich mit entsprechener Möglichkeit auf das aktuelle Objekt der Iteration zugreifen zu können.

entries.box.component.html

<div *ngIf="userTemplate">
  <ng-template ngFor [ngForOf]="elements" [ngForTemplate]="userTemplate">
  </ng-template></div>

Verwendung findet hier wieder das bereits bekannte ng-template mit einer vorrangestellten (optionalen) Prüfung ob einer individuelles Template überhaupt übergeben wurde. Die dann folgende Syntax mag auf den ersten Blick Verwirrend erscheinen. Hier wird ein Template verwendet, mit einer Direktive (ngFor) versehen um die Iteration zu ermöglichen. Mittels Property-Binding werden die Elemente definiert ([ngForOf]] und eine Lauf-Variable für das Element festgelegt (let-e). Diese Möglichkeit verwenden wir sehr oft, nur das Angular uns dafür mit seiner Microsyntax eine etwas einfachere Schreibweise anbietet. So entspricht die Verwendung von *ngFor beispielsweise im Hintergrund exakt dieser Synatx. So sind folgende Schreibweisen funktional völlig identisch:

	<li *ngFor="let e of entries"> Entry: {{e}}</li>
...
    <ng-template ngFor [ngForOf]="entries" let-e>
	<li>Entry: {{e}}</li>
</ng-template>

Die Verwendung der oben gezeigten Komponente ist dann wieder naheliegender:

Verwendung

  <ng-template let-entry>
<ul>
	<li><a (click)='alert(entry)'>{{entry.title}}</a></li>
</ul>
</ng-template>

Lediglich die Defintion der Laufvariablen mittels let-entry ist im Gegensatz zum vorrangegangenen Beispiel neu und dient logischerweise dazu Zugriff auf das aktuelle Iterationsobjekt zu bekommen.

Wie wir im ersten Teil sehen konnten bietet Angular mächtige Möglichkeiten Komponenten wiederverwenbar UND individualisierbar zu machen. Im zweiten Teil tauchen wir noch ein Stück tiefer und werfen einen Blick auf das dynamische Hinzufügen von Komponente mittels ViewContainerRef, ComponentFactory und ComponentFactoryResolver.

Beispiele zum kompletten nachlesen wie immer:

github.com/GEDOPLAN

Brauchen wir noch Java EE? (Oder: Magengrummeln auf der JAX)

Ich sitze gerade auf der JAX in Mainz und versuche mich vorsichtig wieder abzuregen. Aber der Reihe nach.

Auf Konferenzen hört man viele Buzz Words – da ist die JAX keine Ausnahme. Also hört man mehrere Zig Mal pro Tag die Worte Microservices und Container. Auch wenn die genaue Definition von ersterem nicht festgelegt ist, so meint man damit die Erstellung (oder auch Aufteilung) von Enterprise-Anwendungen in überschaubare Teile – im Sinne von DDD irgendetwas zwischen Aggregat und Bounded Context. Es handelt sich also um ein Pattern, das die Kopplung von Softwareteilen aus einem monolithischen Ansatz herauslöst und in die (Betriebs-) Infrastruktur verlagert. Dadurch gewinnt man zweifellos Flexibilität, kann also Anwendungsteile unabhängig voneinander deployen, unterschiedlich skalieren, ggf. sogar in unterschiedlichen Sprachen entwickeln. Das kommt natürlich nicht ohne Kosten wie Kommunikationsaufwand, Resilience, Schnittstellenevolution etc. Container in dem genannten Kontext meint in nahezu allen Fällen Docker, in aller Regel mit einer passenden Verwaltung und Orcherstrierung wie Kubernetes, OpenShift o.ä.

Das ist alles verständlich, spannend und nachvollziehbar. Was allerdings in meinem Augen ziemlich nervt, ist die allgegenwärtige Vermischung von Programm- und Betriebsmodell: Es erscheint als unumstößlliche Gewissheit, dass die alten, großen (und schlechten?)  monolithischen Anwendungen auf schwergewichtigen Java-EE-Servern betrieben werden, während man in einem Docker-Container nur leichtgewichtige (?) SE-Anwendungen laufen lassen kann, die mit allem anderen als Java EE entwickelt werden.

Dies wurde auch gerade in großen Teilen einer Konferenz-Session dargestellt, die den gleichen Titel trägt wie dieser Blog-Post. Ein paar Kernaussagen daraus:

  • „Kernbestandteile von Java EE sind Web (aka JSF), EJB, JCA, JMS. CDI ist als Bindeglied dabei“
    Das verkennt in meinen Augen nahezu komplett die Mächtigkeit von CDI. Dieser Standard/Container kann weit mehr als nur EJBs untereinander und mit der Web-UI zu verbinden. Über seine portable Extension-Schnittstelle ist eine fast grenzenlose Anpassung der gesamten Plattform möglich.
  • „Services baut man in Java EE mit EJB“
    Oder eben CDI, wenn man die speziellen Eigenschaften von EJB nicht benötigt (Timer, Remoting etc.). Wobei EJBs ja auch nicht schwergewichtig sind. Wer das behauptet, hat seit Java EE 5 (2009!) oder 6 nicht mehr drauf geschaut. Ein wesentlicher Punkt in Richtung Einfachheit ist doch Dependency Injection. Die kam für EJB mit der Version 3. Und seit 3.1 kann man auf Local Interfaces verzichten. Heute unterscheiden sich EJB und CDI Beans im Programmcode nur marginal und auch zur Laufzeit sind keine global signifikanten Vor- oder Nachteile zu verzeichnen.
  • „Mit Java-EE kann man nur auf relationale DBs zugreifen“
    Hä? Es gibt zwar kein Standard-API zum Zugriff auf NoSQL-DBs. Die sind ja ihrerseits auch noch gar nicht standardisiert. Aber man man doch das jeweilige proprietäre API benutzen!
  • „Die Struktur einer Java-EE-Anwendung ist starr und fix verknüpft, während in einem Container(-Cluster)  Services dynamisch verbunden und skaliert werden können“ (Man stelle sich dazu links eine Grafik eines Application Servers der Jahrtausendwende vor, rechts ein Schaubild einer aktuellen Kubernetes-Landschaft)
    Das vergleicht nun wirklich Äpfel mit Birnen (oder wie der Speaker es ausdrückte: Rüben mit Kraut). Das eine ist eine aus einem Build herausgefallene, monolithische Anwendung, das andere kleine Einzelservices in separaten Deployments. Warum sollten die Services auf der rechten Seite denn nicht mit dem Java-EE-Stack aufgebaut sein? Ein kleiner Service aus JAX-RS, CDI und JPA ist mit Java EE schnell geschrieben und läuft problemlos in einem Docker-Container. Auch der Footprint der Anwendung mit ca. 100 MB ist nicht wirklich schwergewichtig, auch wenn an der einen oder anderen Laufzeitumgebung vielleicht noch optimiert werden kann.
  • „Ihr müsst jetzt nicht auf Java EE verzichten, wenn eure Ansprüche an Durchsatz und Geschwindigkeit in einem niedrigen oder mittleren Bereich sind“
    Mal abgesehen davon, dass dieser Satz so ähnlich ist wie der, den man auf Sprüche-Karten findet („Das kannste schon so machen, aber dann isses halt Kacke“): Limitierungen in diesem Bereich liegen bspw. in synchronen Konzepten. So belegt ein JDBC-Treiber einen Thread, auch wenn er auf die Reaktion der DB wartet. Das lässt sich mit Aufteilung und Containern nicht verändern. Und im Web(-service)-Bereich bietet Java EE auch asynchrone Vorgehensweisen.

Ich will dem Speaker hier aber gar nicht unterstellen, dass er das so gemeint hat, wie ich (und nicht nur ich) das verstanden habe. Er hat nur den falschen Titel für seine Session gewählt: „Brauchen wir noch Java-EE-Server?“ wäre besser gewesen, denn in meinen Augen geht es ausschließlich um das damit verbundene Deployment- und Betriebskonzept. Das kann kann kritisieren, wenn man möchte. Allerdings läuft ein WildFly oder Payara auch wunderbar in einem Docker-Container – kaum unterscheidbar von einem alternativ darin liegenden Fat JAR.

Also: Tun wir uns allen den Gefallen, nicht die Spezifikation Java EE mit den darin enthaltenen Teilen JAX-RS, CDI etc. und die mögliche Ablaufumgebung Java-EE-Server zu vermischen.

Oder auch (mit einer kleinen Anleihe an eine bekannte Fernsehwerbung von früher): Nichts ist unmööööglich mit Java EE und Docker!

Angular Directive, Hostbinding

Directive sind neben Komponenten eines der zentralen Konzepte in Angular. Anders als Components bringt eine Directive kein eigenes Template mit, sondern erweitert die eigene Anwendung um Funktionalität.

Bevor wir zum eigentlich Hostbinding kommen, werfen wir noch mal einen Blick auf die Grundlagen einer Direktive:

Verwendung:

<div gedLoremIpsumContent>

Implementierung:

@Directive({
  selector: '[gedLoremIpsumContent]',
})
export class LoremIpsumContentDirective {}

Syntaktisch unterscheidet sich eine Direktive kaum von einer Component, analog müssen wir auch hier einen Selektor verwenden mit dessen Hilfe wir unsere Directive später im Template vewenden können. In diesem Fall haben wir uns für einen Attributs-Selector entschieden. Alternativ hätten wir hier auch auf einen Tag-Selector („gedLoremIpsumContent“) oder einen CSS-Selector („.gedLoremIpsumContent“) setzen können.

Das oben gezeigte Beispiel funktioniert, ohne sichtbaren nutzen, tadellos und erzeugt für das DIV das unsere Directive verwendet auch eine entsprechende Objektinstanz. Innerhalb unserer Direktive können wir nun Einfluss auf das DOM-Element nehmen auf dem unsere Direktive verwendet wird (der so genannten „Host“). Hierzu stehen uns 4 Möglichkeiten zur Verfügung.

ElementRef

Die Klasse „ElementRef“ bietet uns eine entsprechende Schnittstelle an um auf das zugrunde liegende HTML-Element zugreifen zu können. Nach der Injektion über den Konstruktor liefert die so erhaltene Instanz mittels der Methode „nativeElement“ den Zugriff auf die DOM-API. Hierüber sind wir nun in der Lage die gewünschten Manipulationen am Element vorzunehmen.

  constructor(element: ElementRef) {
    element.nativeElement.style.cursor = 'pointer';
  }

Der direkte Zugriff auf die DOM API sollte jedoch nur im äußersten Notfall verwendet werden. Angular ist so konzipiert das der Browser nur eines der möglichen Anzeige-Medien ist. Hier sind auch native Clients, mobile Geräte und serverseitiges Rendering möglich, die keinen zugrundeliegenden DOM-Baum haben und auch lässt sich eine Direktive die sich direkt auf das native Element stützt nicht per Unit-Tests testen.

Renderer

Der Renderer ist im Grunde eine Abstraktion für den direkten Zugriff auf die DOM-API und sollte immer vorrangig, vor der direkten Verwendung des „nativeElement“ , verwendet werden. Diese Klasse bietet diverse Methoden an um das zugrunde liegende DOM-Element zu manipulieren.

  constructor(element: ElementRef, renderer:Renderer2) {
    renderer.setStyle(element.nativeElement, "border", "1px solid black");
  }

Render2-API

HostBinding

Das HostBinding bietet uns die Möglichkeit das Attribut von unserem Host (in diesem Beispiel dem DIV-Element) direkt an eine eine Property zu binden, ohne das wir uns im weiteren Programm-Verlaufen darum kümmern müssen das Änderungen des Wertes auch am DOM-Elemente aktualisiert werden.

  @HostBinding('innerHTML')
  innerHTML: string;

  ngOnChanges() {
   this.innerHTML = 'Hello World';
  }

Alternativ zur (empfohlenen) Verwendung des @HostBinding Decorators kann das Binding auch bei der Deklaration der Directive gesetzt werden:

@Directive({
  selector: '[gedLoremIpsumContent]',
  exportAs: 'loremIpsumContent',
  host: {
    'class': 'demo',
    '(mouseenter)': 'mouseEnter()'
  }
})

HostListener

HostListener bieten uns analog zum HostBinding Zugriff auf Events die auf dem Host auftreten können. Dazu verwendenden wir den  @HostListener-Decorator an eine unserer Methoden und legen fest welches Event verarbeitet werden  und welche Parameter übergeben werden sollen

  @HostListener('click', ['$event'])
  onHostClick($event) {
   this.innerHTML="New Value";
  }

Mit diesen Möglichkeiten bietet Angular alles um sicher und einfach direkt oder indirekt mit den Elementen des DOM-Baumes zu arbeiten. Das Ganze als Live-Beispiel gibt es wie immer in

Github: https://github.com/GEDOPLAN/angular-directive

Angular, Lifecycle Methoden

Mittels Lifecycle Methoden bietet Angular uns die Möglichkeit an bestimmten Stellen des Lebenszyklus einer Komponente ein zu greifen um z.B. Initialisierungen vor zu nehmen. Dieser Beitrag stellt die unterschiedlichen Methoden kurz vor.

demo.png

Die Implementierung von Lifecycle Methoden ist denkbar einfach. Es genügt innerhalb der eigenen Komponente eine entsprechende Methode zu implementieren und Angular sorgt dafür das diese aufgerufen wird. Bei der Arbeit mit TypeScript ist es allerdings empfehlenswert das entsprechende Interface zu verwenden, sodass der Compiler bereits  darauf aufmerksam machen kann das die gewünschte Lifecycle Methode eventuell nicht oder nicht korrekt implementiert ist.

import {Component, OnInit,} from '@angular/core';

@Component({...})
export class BoxComponent implements OnInit {

  ngOnInit() {...}

  ngOnChanges() {...}
}

Werfen wir einen Blick auf die von Angular zur Verfügung gestellten Methoden:

constructor
Keine „echte“ Lifecycle Methode von Angular, hier sollte möglichst wenig Logik implementieren werden. Die primäre Aufgabe innerhalb des Konstruktors besteht darin Dependency Injection zu steuern und zu verarbeiten

ngOnInit
Dient zu Initialisierung der eigener Komponente. An dieser Stelle sind eventuell vorhandene @Input-Parameter bereits vorhanden. Grundlegende Initialisierungen gehören in diese Methode.

ngOnChange
Diese Methode wird einmalig nach der ngOnInit-Methode aufgerufen und dann bei jeder Änderung der Input-Parameter. Müssen die Input-Parameter zur Initialisierung unserer Komponente in irgendeiner Art und Weise verarbeitet werden (z.B. eine Datums-Komponente erhält einen Date-String, arbeitet intern aber mit einem Date-Objekt) sollten diese Aufgaben nicht in der ngOnInit Methode, sondern hier implementiert werden.

ngAfterContentInit
Initialisierung der @ContentChildren-Komponenten. Die View-Query zum auffinden der mittels @ContentChildren annotierten Kind-Komponenten wird vor dieser Methode durchgeführt, sodass alle Content- (nicht aber die View-)-Children zur Verfügung stehen

ngAfterViewInit
Initialisierung der @ViewChildren-Komponenten. Die View-Query zum auffinden der mittels @ViewChildren annotierten Objekte wird vor dieser Methode durchgeführt, sodass alle nun auch alle View-Children zur Verfügung stehen

ngOnDestroy
Wird aufgerufen wenn die Komponente aus dem DOM-Baum entfernt wird, z.B. bei einem Routing oder Ausblenden über *ngIf.

ngAfterContentChecked | ngAfterViewChecked.
Angular Change Detection Methoden, kann Verwendet werden um den Lebenszyklus/ChangDetection zu protokollieren

ngDoCheck
Methode in der eine eigene Change Detection implementiert werden kann. Diese Methode sollte nur in Ausnahmefällen verwendet werden und mit möglichst wenig Logik versehen werden, da diese Methode sehr häufig aufgerufen wird. Ein Beispiel wann so etwas nötig ist: die „ngOnChange“ Methode wird nur dann bei Änderungen der Input Parameter aufgerufen wenn sich der Parameter selber ändert, wird hier ein Objekt verwendet und es ändert sich lediglich ein Attribut des Objektes wird die ngOnChange Methode nicht aufgerufen.

Demo-Projekt @ Github