Angular, Quarkus und Kotlin
Von Carsten
Um ein paar Einblicke in Angular - und einen Vergleich zu React zu bekommen, habe ich heute eine sehr einfache Webanwendung mit Angular Frontend erstellt, die eine einfache Anfrage an ein Quarkus-Backend in Kotlin schickt und die Antwort anzeigt. Der vollständige Sourcecode kann unter https://www.kopis.de/git/carsten/helloworld-kotlin angesehen werden.
Quarkus Service
Das Quarkus Backend besteht nur aus einer einzelnen Resource mit einer einzelnen Methode. Die Methode gibt eine sehr einfache JSON Response zurück, mit einem variablen Anteil aus einem Query Parameter. Da ich schon Erfahrung mit Quarkus habe, war das hier nur ein kleine Einblick, wie eine Resource in Kotlin aussieht.
import javax.ws.rs.DefaultValue
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.QueryParam
import javax.ws.rs.core.MediaType
@Path("/hello")
class ExampleResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
fun hello(@QueryParam("name") @DefaultValue("stranger") name: String) = Hello("Hello from RESTeasy, ${name}")
data class Hello(val message: String)
}
Angular Frontend
Das Angular Frontend war schon interessanter, da meine letzten Schritte mit dem Framework schon über 3 Jahre zurück liegen. Die letzten 3 Jahre habe ich fullstack mit React entwickelt und komme mit dem dort verwendeten Modell ganz gut zurecht. Angular - vor allem in Verbindung mit Typescript - sieht im direkten Vergleich mit meinem aktuellen Programmierstil in React+javscript sehr umständlich bzw. sehr ausführlich aus. Fast so wie Java 8 im Vergleich zu Java 17 oder Java mit Lombok. Das könnte natürlich auch ein Problem eines kleinen Spielprojekts sein und erst bei größeren Projekten werden die Vorteile sichtbar.
greeting.service.ts
Um die Kommunikation mit dem Backend zu kapseln, habe ich mit ng generate service greeting
einen
neuen Service angelegt und den sehr einfachen Aufruf des Backends dort abgelegt:
import {Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {catchError, Observable, of} from "rxjs";
import {GreetingResponse} from "./greetingresponse";
@Injectable({
providedIn: 'root'
})
export class GreetingService {
constructor(
private http: HttpClient
) {
}
getGreeting(username: string): Observable<GreetingResponse> {
return this.http.get<GreetingResponse>(`/hello?name=${username}`)
.pipe(catchError(this.handleError<GreetingResponse>('getGreeting', {message: ''})));
}
private handleError<T>(operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
console.error(error);
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
}
Bisher habe ich solche Zugriffe eher in sehr einfachen ES6 Modulen abgelegt, die einfach die JS fetch API verwendeten. Den Angular HttpClient werde ich mir auf jeden Fall nochmal genauer ansehen.
app.component.ts
Im Moment betrachte ich diese Komponente als Teil eines MVC, das Property greeting
ist in meinem
Kopf das Model, und ich lege dort Daten zur Anzeige/Eingabe auf der einzigen Seite meiner kleinen
Spielanwendung ab.
import {Component} from '@angular/core';
import {GreetingService} from "./greeting.service";
import {Greeting} from "./greeting";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
greeting: Greeting = {
username: 'stranger',
response: ''
}
constructor(
private greetingService: GreetingService
) {
}
getGreeting() {
this.greetingService.getGreeting(this.greeting.username)
.subscribe(response => this.greeting.response = response.message);
}
}
app.component.html
Auf der einzigen Seite der Anwendung zeige ich ein Eingabefeld, einen Button und die empfangene Nachricht an:
<div>
<label for='username'>Your name:</label>
<input id='username' [(ngModel)]='greeting.username' placeholder='Enter your username...'>
<button type='button' (click)='getGreeting()'>Submit</button>
<div *ngIf='greeting.response'>{{greeting.response}}</div>
</div>
Das Property greeting
enthält in meinem Fall sowohl den eingegeben Benutzernamen als auch die
Response vom Service. Der Button ruft die Methode getGreeting
in meiner App Komponente auf, die
wiederum den GreetingService
benutzt, um einen HTTP Request an das Backend zu schicken. Vielleicht
sollte ich noch einen etwas komplexeren Backend-Call einbauen, damit klar wird, welche Rolle die
Aufteilung in Komponente und Service spielt.
Für das erste kleine Spielprojekt in 1h Ausprobieren bin ich ganz zufrieden. Der Entwicklungsflow unterscheidet sich nicht besonders von meinem aktuellen Flow mit Spring bzw. Quarkus und React. Etwaige Vorteile von Typescript hat die komplexere Struktur von Angular aufgefressen, die einfache Struktur von ES6 Module gefällt mir im Moment noch besser. Die nächsten Erweiterungen dieser Spielanwendung machen mir das hoffentlich noch etwas deutlicher.