Angular, testen mit Karma/Jasmine

Anwendungen zu testen ist ein leidiges Thema und wird gerne aus Zeitgründen vernachlässigt. Jeder der schon mal ein größeres Refactoring durchgeführt hat wird aber eine gute Testabdeckung zu schätzen wissen. Angular macht es uns einfach dank AngularCLI zu einer getesteten Anwendung zu kommen.

app.png

Projekte die mit AngularCLI erstellt und entwickelt werden liefern schon sehr viel was nötig ist um seine Anwendung zu testen. Neben einer einheitlichen Anwendungsstruktur generiert uns das Werkzeug auch Test-Hüllen die wir lediglich mit Leben füllen müssen. So kann eine solche Anwendung direkt per Befehl: ng test einem Testlauf unterzogen werden. Da wir bis hierher noch kaum einen Handschlag getan haben sind diese Tests natürlich rein technischer Natur, so wird zumindest geprüft ob die Templates der Komponenten geparst werden können.  Die Test-Dateien die während des Testings herangezogen werden erhalten bei der Generierung den Postfix „.spec.ts“. Hier ein einfaches Beispiel was, ganz ohne eigene Arbeit, von Angular generiert wird:

describe('HelloWorldComponent', () => {
  let component: HelloWorldComponent;
  let fixture: ComponentFixture<HelloWorldComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ HelloWorldComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HelloWorldComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Was wir hier sehen ist ein Jasmine-Testfile in dem Gruppen von Testfällen erstellt werden (describe) in denen dann einzelne Tests (it) definiert werden. AngularCLI fügt bereits die benötigten Initialisierung ein in der die Komponente, die es zu testen gilt, erstellt wird und auch einen entsprechenden Testfall um die erfolgreich Erstellung zu prüfen.

Komponente zum Test

Schauen wir uns also den Test einer Komponente an. Die Basis bildet eine sehr einfache Komponente die eine Liste von Benutzern entgegen nimmt und lediglich einige der Informationen darstellen soll ( aus Platzgründen sparen wir uns hier die Implementierung dieser Komponente, zu finden ist sie aber im Github )

  it('should show unser infos', () => {
    component.users = [
      {username: 'mocked', mail: 'mocked@mock.de'}
    ];

    fixture.detectChanges();
    const compiled: Element = fixture.debugElement.nativeElement;

    const firstUserName = compiled.querySelectorAll('h2').item(0).innerText;
    expect(firstUserName).toBe('mocked');
  });

Dank der Initialisierung der Testdatei haben wird die Komponente bereits für uns erzeugt. Diese könne wir nun mit Daten versorgen (Zeile: 2) um das Verhalten der Komponente zu testen. Die User-Daten werden in der „echten“ Anwendung von der übergeordneten Komponente übergeben und werden vermutlich über einen Rest-Service gelesen. An dieser Stelle ist die Datenherkunft aber irrelevant, wir konzentrieren uns beim testen der Komponente lediglich auf das Verhalten der Komponente selbst, sodass wir in aller Regel darauf verzichten werden hier irgendwelche Services auf zu rufen um Daten zu bekommen.

Nach dem setzen der benötigen Daten rufen wir fixture.detectChanges();  damit Angular seinen Change Detecetion durchläuft. Anschließend können wir auf das native Element zugreifen  (Zeile: 7 ) und mittels Queries die Ausgabe im Template auf ihre Richtigkeit prüfen.

Formular zum Test

Mit diesem Ansatz lasse sich jetzt auch Formulare testen. In unserem Fall haben wir uns für das Template-Driven Design entschieden. Um dies zu testen benötigen wir Zugriff auf das Formular, das wir in unserer Komponente per

  @ViewChild(NgForm)
  form: NgForm;

injizieren lassen können. Somit erhalten wir Zugriff auf das Fomular und können dieses über die entsprechende API mit Daten versorgen. Unser Test z.B. prüft ob die Benutzer Liste korrekt gefiltert wird wenn das entsprechende Feld mit der maximalen Anzahl der an zu zeigenden Benutzer gefüllt wird:

  it('should load limit users', async(() => {
    const maxUser = component.form.form.get('maxUser');
    const btn = fixture.nativeElement.querySelector('button[name="loadBtn"]');

    expect(maxUser).toBeTruthy();
    expect(btn).toBeTruthy();

    maxUser.setValue(1);
    btn.click();
    fixture.detectChanges();
    let elecount = fixture.nativeElement.querySelectorAll('.portfolio-item').length;
    expect(elecount).toBe(1);
  }));

Wir erhalten Zugriff auf das „maxUser“ Feld über das Formular. Den Button ermitteln wir über die bereits gesehene „querySelector“ Methode. Nun können wir das Feld mit Daten versorgen und einen Button-Klick emulieren um dann zu prüfen ob die erwartete Anzahl an Benutzer auf der Oberfläche dargestellt wird.

Komponente ohne Service

In aller Regel verwenden unserer Komponente Services um Daten z.B. von einer Rest-Schnittstelle zu beziehen. Diese Services können entweder im NgModule als Provider registriert werden oder wie hier zu sehen auf der Ebene der Komponente:

 @Component({
  ...
  providers: [UserService]
})
export class AppComponent {...}

Der Test unserer AppComponent wäre nun diekt von der korrekten Funktion des UserServices abhängig. Wollen wir unsere Komponente aber autark testen kann dieser Service durch eine Mock Implementierung ausgetauscht werden. Ein Mock zum UserService implementiert dieselbe Schnittstelle wie der „echte“ Service, führt aber zum Beispiel keine HTTP-Requests durch, sondern liefert definierte Rückgaben:

 @Injectable()
export class UserServiceMock {

  constructor(private http: Http) { }

  getUsers(): Observable<any[]> {
    return Observable.of(USER_DATA);
  }
}

export const USER_DATA = [...]

Nun muss lediglich sicher gestellt werden, dass unser Mock-Service herrangezogen wird wenn unser Test die Komponente instanziiert. Sollten wir den Service über das NgModule registriert haben reicht in unserem Test die Angabe eines eigenen Provider-Arrays (s. „Service zum Test“, Einbindung von HttpModule). In unserem Fall deklariert die Komponente über ein eigenes Provider-Array seine Abhängigkeiten die beim testen nun „überschrieben“ werden müssen. Dazu bietet uns „TestBed“ eine entsprechende Methode.

describe('AppComponent', () => {
  let component: AppComponent;
  let fixture: ComponentFixture<AppComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpModule, FormsModule],
      declarations: [
        AppComponent, UserListComponent
      ]
    }).overrideComponent(AppComponent, {
      set: {
        providers: [
          { provide: UserService, useClass: UserServiceMock }
        ]
      }
    }).compileComponents();
  });

Service zum Test

Als Basis dient folgender Service der User-Daten abruft:

@Injectable()
export class UserService {

  constructor(private http: Http) { }

  getUsers(): Observable<any[]> {
    return this.http.get('http://jsonplaceholder.typicode.com/users').map(r => r.json());
  }
}

Der entsprechende Test ist ebenfalls sehr überschaubar:

  it('should load a least some users', async(inject([UserService], (service: UserService) => {
    expect(service).toBeTruthy();
    service.getUsers().subscribe(r => {
      expect(r.length).toBeGreaterThan(5);
    });
  })));

Zwei Dinge kommen hier hinzu die für den korrekte Ablauf wichtig sind. Zum einen wrappen wir unsere Test-Funktion in einen „async“-Funktion. Damit weisen wir unseren Testlauf an auf die Abarbeitung von asynchronen Methoden zu warten. Lasse wir dies weg würde unser Test immer erfolgreich durchlaufen, da unsere Prüfung asynchron über die „subsribe“ Methode implementiert ist und der Test bereits als erfolgreich deklariert worden wäre ohne auf die Antwort zu warten.

Würden wir den Test nun mit der Standard Initialisierung von Angular laufen lassen, würde wir einen Fehler erhalten: Error: No provider for Http! Um den Fehler richtig zu interpretieren ist es wichtig zu verstehen das im Falle eines Testes Angular ein eigenes Modul aufbaut und nicht das „normale“ NgModule verwendet welches wir ja für die Anwendung definieren. Damit wird klar: das HTTPModule ist war im Modul unserer Anwendung deklariert nicht aber in unserem speziellen Test-Modul. Solche Abhänigkeiten müssen hier speziell für den Test definiert werden. Dazu erweitern wir die inital von AngularCLI generierte Definition unserer beforeEach-Methode:

describe('UserService', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpModule],
      providers: [UserService]
    });
  });
...

und importieren das HttpModule auch für unserern Testfall.

Streng genommen verletzten wir mit diesem Test allerdings den Gedanken eines Unit-Testes. Wir rufen einen externen Service auf der autark gepflegt wird und damit ist unserer Testfall nicht nur von der Logik unserer eigenen Komponente abhänig sondern auch von der adressierten Rest-Schnittstelle. Eine Änderung der gelieferten Daten über diese Schnittstelle würde bei uns nun zu einem Fehler führen obwoh unser Programm immer nocht korrekt arbeitet. Das sollte man bedenken wenn solche Tests imlementiert werden.

Services zum Test, aber ohne externe Aufrufe bitte!

Um generell Aufrufe über HTTP zu unterbinden und stattdessen eine definierte Antwort bereit zu stellen bietet Angular die Klasse „MockBackend“ welche das normale XHRBackend mit einer Implementierung ersetzt.

import { TestBed, inject, async } from '@angular/core/testing';
import { XHRBackend, ResponseOptions, Response } from '@angular/http';
import { MockBackend } from '@angular/http/testing';

import { UserService } from './user.service';
import { HttpModule } from '@angular/http';;

describe('UserService (with mocked Backend)', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpModule],
      providers: [
        { provide: XHRBackend, useClass: MockBackend },
        UserService
      ]
    });
  });

  it('should load a least some users', async(inject([UserService, XHRBackend], (service: UserService, mockBackend: MockBackend) => {
    mockBackend.connections.subscribe((connection) => {
      connection.mockRespond(new Response(new ResponseOptions({
        body: JSON.stringify([{"username": "mock"}])
      })));
    });

    expect(service).toBeTruthy();
    service.getUsers().subscribe(r => {
      console.log(r);
      expect(r.length).toBeGreaterThan(0);
    });
  })));
});

Wir greifen mit den „Providers“ in die Depedency Injection von Angular ein und bringen so das angesprochene MockBackend ins Spiel. Nun können wir in unseren Testfällen die Instanz des MockBackend injizieren lassen und auf eingehende Verbindungen reagieren. Hier haben wir nun die Möglichkeit definierte HTTP-Response zu schicken, ohne das eine echte Verbindung nach außen aufgebaut wird

Spies

Ein weiteres nettes Feature von Jasmine ist es so genannte „Spies“ auf bestimmte Methoden zu legen. Mit dessen Hilfe können wir Methoden-Aufrufe aufzeichnen und so prüfen ob Interaktionen mit der Komponente auch korrekt z.B. zu einem Service durchgreicht werden:

   it('should call load method on button click', async(() => {
    const userService = (<any>component).userService;
    const btn = fixture.nativeElement.querySelector('button[name="loadBtn"]');

    const spy = spyOn(userService, 'getUsers');
    btn.click();
    btn.click();
    btn.click();

    expect(spy.calls.count()).toBe(3);
  }));

Dieser Test funktioniert wie erwartet. Wichtig zu beachten ist aber folgendes: ein solcher Spy führt dazu das die Service-Methode „getUsers“ gar nicht aufgerufen wird. Wir simulieren hier also den Methodenaufruf nur. Falls wir in diesem Testfall auch Prüfungen implementieren wollen die das Ergebniss dieser Methoden-Aufrufe berücksichtigt muss dies mit folgendem Methoden-Aufruf festgelegt werden:

spy.and.callThrough();

Test-Code zu schreiben und vor allem aktuell zu halten stellt immer ein Overhead da der im Projekt berücksichtigt werden muss. Für komplexe Anwendungen ist das Testen jedoch ein wichtiger Baustein für eine stabile Software. Angular tut sein Bestes die Arbeit mit Tests so einfach zu machen wie möglich, nur das Schreiben der Testfälle ist und bleibt eine Aufgabe des Entwicklers.

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

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

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