Rest – HATEOAS + HAL

REST-Webservices sind wohl die am weitesten verbreitete Schnittstellentechnologie die im WEB zu finden ist. Dank JSON als Datenformat bietet REST eine zustandslose und leichtgewichtige Möglichkeit Daten zwischen Anwendungen aus zu tauschen. HATEOAS (Hypermedia as the Engine of Application State) ist nun im Grunde keine weitere Technologie, sondern ein Konzept wie eine REST-Schnittstelle aufgebaut werden sollte.

Der Grundgedanke hinter HATEOAS ist das Clients in der Lage sind die Funktionen der Anwendungs-API zu verwenden ohne das diese fest verdrahtet im Client-Code sein müssen. Vergleichbar ist dies mit der menschlichen Nutzung einer Webseite. Wir besuchen einen Internetauftritt, erhalten Informationen, aber auch weiterführende Links über die wir weitere Funktionen/Seiten abrufen können. Das Prinzip lässt sich nahezu eins zu eins auf HATEOAS anwenden. Anstatt lediglich die angeforderten Informationen in der Datenstruktur von JSON zu erhalten informiert uns die Anwendung ebenfalls über weiterführende Aktionen und Links.

Ein einfaches Beispiel ist z.B. die Abbildung von JPA Entitäten im JSON Format. Nehmen wir ein sehr einfaches Datenmodell aus zwei Entitäten: User und Message. Die Entitäten besitzen eine Bi-Direktionale Verbindung:

@Entity
public class Message {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;

  private Date date;

  private String message;

  @ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST })
  private User user;

------------------------------------------------------------------------

@Entity
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;

  private String username;

  @OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
  private List<Message> messages;

Bereits hier müssen wir uns Gedanken über die Umwandlung in ein JSON Format machen: ohne zusätzlichen Eingriff von unserer Seite könnten diese Entitäten bereits nicht in ein JSON Format umgewandelt werden, da die Bi-Direktionale Relation zu einer Endlosschleife bei der Umwandlung führen würde. Neben die Bildung von DTOs richten sich die Möglichkeiten um dieses Problem zu beheben nach dem verwendeten JSON Parser (Jackson bietet z.B. Annotationen an um Felder zu ignorieren, Sub-Views aus zu liefern,  solche Relationen zu kennzeichnen oder diese lediglich mit der entsprechenden ID aus zu liefern). Eine klassischer JSON Output für unser „Message“-Objekt könnte nach diesem Prinzip so aussehen:

{
     "date": "1970-01-01T00:00:00.000+0000",
     "message": "Hello World",
     "user": 2
}

Anstatt das Userobjekt mit aus zu liefern bietet das JSON hier nur die entsprechende ID an. Wenn der Client auch die Benutzer-Information benötigt muss dieser wissen das es sich bei dieser ID um eine User-ID handelt, zudem muss die entsprechende URL zum Abruf der User-Daten (z.B. “ http://…/user/2 „) im Client-Code festgelegt sein.

HAL

Zur Unterscheidung: HATEOAS ist der Architektur-Ansatz, HAL ist eine Spezifikation zur JSON-Syntax der Link-Relationen. Neben HAL gibt es noch einige Alternativen (Collection+JSON, Hydra…). HAL findet z.B. Verwendung in „Spring HATEOAS“. Mittels „HAL“ können die Informationen nun verlinkt werden und der Client damit in die Lage versetzt werden die benötigen zusätzlichen Informationen ab zu rufen, ohne die konkreten URLS selber zusammen zu setzen.

Unser Message-Objekt könnte mittels „HAL“ dann wie folgt aussehen:

{
    "date": "1970-01-01T00:00:00.000+0000",
    "message": "Hello World",
    "_links": {
        "self": {
            "href": "http://localhost:8080/msg/2"
        },
        "user": {
            "href": "http://localhost:8080/msg/2/user"
        }
    }
}

Header Links

Eine andere Möglichkeit besteht darin Links im HTTP Header zu verankert. Dies lässt sich auch mit JAX-RS direkt erledigen. Ein einfaches Beispiel um innerhalb der Rest-Methode einen Link der Response hinzuzufügen könnte so aussehen:

  @Context
  UriInfo uriInfo;

  @GET
  @Path("hal")
  public Response getHelloHAL() {
    DemoEntity demo = new DemoEntity(1, "Hello World");
    return Response.ok(demo).link(uriInfo.getBaseUri().resolve("login"), "login").build();
  }

Der Header wird dann um einen entsprechenden Eintrag erweitert und enthält den angegebenen Link:

...
link <http://localhost:8080/angular-jwt-1.0-SNAPSHOT/rest/login>; rel="login"
...

Im nächsten Artikel werfen wir einen Blick auf Spring-Boot und dessen Möglichkeiten HATEOAS und HAL mit sehr wenig Aufwand in der eigenen Anwendung zu verwenden.

Advertisements

JSON Web Tokens – JWT – Angular

Im ersten Teil haben wir einen Blick auf JWT im Zusammenspiel mit Java EE geworfen. Heute soll es um die Client-Seite gehen und die Verwendung von JWT im Kontext von Angular (v. 4)

app.png

Für Angular gibt es inzwischen einige Zusatzbibliotheken die den Umgang mit JWT wesentlich vereinfachen. Eine davon werden wir unserem Projekt wie gewohnt über NPM hinzufügen:

npm install @auth0/angular-jwt

Diese neue Version ist ab Angular 4 verfügbar und verwendet zur Implementierung die eingeführten HTTP-Interceptoren. Die anschließende Verwendung ist denkbar einfach. Wir konfigurieren JWT über unser app.module und definieren eine Methode welche den JWT Token zur Verfügung stellt. In diesem Beispiel legen wir diesen nach einem Login in den LocalStorage ab und rufen diesen bei der Initialisierung der Anwendung ab:

 

import { JwtModule } from '@auth0/angular-jwt';

export const getToken = function() {
  return localStorage.getItem('JWT-TOKEN');
}

@NgModule({
    ...
    JwtModule.forRoot({
      config: {
        tokenGetter: getToken,
        whitelistedDomains: ['localhost:8080']
      }
    })
  ]
export class AppModule { }

Hier zu sehen ist auch die benötigte Deklaration der Domains an die unser Token geschickt werden soll (da viele Anwendungen mit mehreren Servern kommunizieren und nicht alle Requests mit den zusätzlichen Headern versehen werden sollen).

In Folge dieser Deklaration erhalten alle unsere Requests an die definierten Hosts einen entsprechenden Authentifikation-Header, ganz automatisch:

request_header_jwt

Token? Her damit !

Natürlich muss dieser Token erst mal den Weg in unsere Anwendung finden. Der Token wird vom Server bereitgestellt und lässt sich mit einer sehr einfachen Login Methode implementieren:

  login(username: string, password: string): Promise<boolean> {
    return new Promise((resolve, reject) => {
      this.http
        .post('http://localhost:8080/angular-jwt-1.0-SNAPSHOT/rest/login'
           , { username, password }, { responseType: 'text' })
        .subscribe((resp: string) => {
          localStorage.setItem('JWT-TOKEN', resp);
          resolve(true);
        }, error => reject);
    });
  }

Du? Nicht. Guards

Mit dem Vorhandensein dieses Tokens lässt sich nun auch sehr leicht der Zugriff auf bestimmte Teile der Seite verhindern. Dazu verwenden wir so genannte Guards, die beim Routing greifen und prüfen ob eine bestimmte Route überhaupt verwendet werden darf:

Guard

export const APP_ROUTES: Routes = [
  ...
  { path: 'protected', component: ProtectedComponent, canActivate: [AuthGuardService] },
  { path: 'notallowed', component: NotAllowedComponent }
]

Route-Definition

@Injectable()
export class AuthGuardService implements CanActivate {

  constructor(private router: Router) { }

  canActivate(): boolean {
    const allowed = localStorage.getItem('JWT-TOKEN') !== null;

    if (!allowed) {
      this.router.navigateByUrl('notallowed');
    }
    return allowed;
  }
}

JWT ist einer der weit verbreiteten Techniken für die Zustandslose Authentifizierung und den Austausch verschlüsselter Daten. Angular bietet zusammen mit der hier gezeigten Bibliothek eine sehr einfache Möglichkeit diese Technik zu nutzen.

Live? Farbe? github/GEDOPLAN

JSON Web Tokens – JWT – Java

JWT (Json Web Token) ist ein Technik zum Austausch von gesicherten Daten die Zustandslos über HTTP übertragen werden können. In dieser zweiteiligen Serie werfen wir einen kurzen Blick auf die Verwendung zur Authentifizierung zwischen einer Java EE und Angular Anwendung.

Um JWT in unserer Anwendung einzusetzen verwenden wir eine zusätzliche Bibliothek die wir mittels Maven wie gewohnt einbinden können:

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>

Mit Hilfe dieser Bibliothek sind wir nun in der Lage JWT Tokens zu generieren. Hier ein Beispiel zur Erzeugung dieser Tokens und die Verwendung der API:

  public String generateJWTToken(String user) {
    String token = Jwts.builder()
        .setSubject(user)
        .claim("groups", new String[] { "admin", "customer" })
        .claim("mail", "dominik.mathmann@gedoplan.de")
        .signWith(SignatureAlgorithm.HS512, System.getProperty("JWT-KEY"))
        .compact();
    return token;
  }

Der Payload des JWT-Tokens enthält so genannte „claims“, Gruppen von Informationen die wir beliebig setzen können. Es existieren einige reservierte Claims die spezifiziert sind. Einen davon sehen wir im obigen Beispiel, so ist das „Subject“ ein solcher Claim der den Benutzer identifiziert. Es folgen dann einige eigens definierte Claims: die Gruppen des Benutzers (die natürlich normalerweise z.B. aus einer Datenbank kommen) und eine Mail Adresse. Anschließen „verpacken“ wir diese Informationen in einen HS512 Codierten Token der mit einem Schlüssel signiert wird. Es existieren noch einige weitere Möglichkeiten, so lässt sich zum Beispiel eine Gültigkeit festlegen, nach der dieser Token seine Gültigkeit verliert.

Das Ergebnis sieht für das Beispiel dann wie folgt aus:

eyJhbGciOiJIUzUxMiJ9.
eyJzdWIiOiJkZW1vIiwiZ3JvdXBzIjpbImF
kbWluIiwiY3VzdG9tZXIiXSwibWFpbCI6ImRvbWluaWsubWF0aG1hbm5AZ2Vkb3BsYW4uZGUif.
70b3xatjS8za28ekb1eQRo-wgB2Y7mKSqXSc6_IcIOmDsmR5nJbKZXqKJeegtwzk7i0rnpvgK50dgqdWrN9H6g

Durch den „.“ getrennt lässt sich hier auch der allgemeine Aufbau eines JWT-Tokens erkenne, der aus 3 Teilen besteht:

  • Header, enthält den Typ des Tokens und die Verschlüsselungs-Variante
  • Payload, enthält unsere Claims
  • Signature, zur Validierung des Tokens

Dieser Token kann nun zum Client übertragen werden und sollte bei jedem Request an den Server zurück übermittelt werden um zu prüfen ob dieser noch gültig ist und ob der identifizierte Benutzer berechtigt ist die angeforderte Resource zu erhalten. Diese Übertragung erfolgt in aller Regel im HTTP Header „Authorization“ im Format:

„Bearer [Token]“

JAX-RS mit JWT

Um einen solchen JWT Token nun zur Identifizierung und Absicherung unserer Rest-Schnittstellen zu verwenden bietet es sich an einen JAX-RS Filter zu verwenden (ähnlich den Inteceptoren von CDI). Dazu implementieren wir zuerst eine Annotation die dann mit einer entsprechenden Filter-Implementierung versehen wird:

Annotation

@javax.ws.rs.NameBinding
@Retention(RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface JWTAuthed {
}

Filter-Implementierung

@Provider
@JWTAuthed
@Priority(Priorities.AUTHENTICATION)
public class JWTAuthedFilter implements ContainerRequestFilter {

  @Inject
  private JWTService jwtService;

  @Override
  public void filter(ContainerRequestContext requestContext) throws IOException {
    String token = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);

    try {
      jwtService.valid(token.split(" ")[1]);
    } catch (Exception e) {
      requestContext
      .abortWith(Response.status(Response.Status.UNAUTHORIZED)
      .build());
    }
  }

  public void valid(String token) {
    JwtParser signed = Jwts.parser().setSigningKey(System.getProperty("JWT-KEY"));

    String username = signed.parseClaimsJws(token).getBody().getSubject();
    System.out.println("Request is JWT-signed with user: " + username);
  }
}

Nun reicht es unsere Annotation einfach an die zu sichernden Methoden zu setzen und der Container führt automatisch eine entsprechende Validierung und Identifizierung unseres Tokens durch und würde in einem Fehlerfall einen entsprechenden Fehler auswerfen.

  @GET
  @JWTAuthed
  public DemoEntity getHello() {
    ...
  }

Ich will nicht mehr, Logout

Rein technisch bleiben diese Token unendlich gültig so lange der Signing-Key nicht verändert wird ( und falls kein Zeitstempel definiert wurde ab wann der Token ungültig wird ) Ein klassischer Logout ist somit erst mal nicht möglich. Hierzu gibt es diverse Möglichkeiten dies dennoch zu implementieren. Zum Beispiel  könnte der Login/Logout Status in der Datenbank festgehalten werden und zusätzlich geprüft werden, was jedoch bei jedem Request zu einer Datenbankabfrage führen würde. Ein sehr einfacher Weg, der die Zustandslosigkeit ein wenig zunichte macht wäre das Vorhalten von gültigen Token in einer Application-Scoped Bean. Sollte die Anwendung in einer geclusterten Umgebung ausgeführt werden müsste diese Information allerdings repliziert werden, außerdem führt ein Neustart des Anwendungsservers dazu das alle Benutzer sich erneut einloggen müssen

private List<String> validJWTTokens = new ArrayList();

  public String generateJWTToken(String user) {
    String token = ...

    this.validJWTTokens.add(token);
    return token;
  }

  public void valid(String token) {
    if (!this.validJWTTokens.contains(token)) {
      throw new RuntimeException("Token is not valid anymore");
    }
     ...
  }

JWT in Java ist dank JJWT kein Hexenwerk und lässt sich relativ einfach in die eigenen Anwendung einbringen um Authentifizierung durchzuführen oder verschlüsselte Informationen aus zu tauschen.

Alles Live in Farbe auf github.com/GEDOPLAN

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