Inhalt

  1. Einführung
  2. CLI und Setup
  3. Ordnerstruktur
  4. Komponenten
  5. Attribute und Interpolation
  6. Inputs
  7. Outputs und Events
  8. Modelle
  9. Directives
  10. Services
  11. Observables
  12. Subjects und Subscriptions
  13. HTTP Client
  14. Forms
  15. Routing
  16. Weiterführendes Material

Einführung

Angular ist ein Frontend-Framework für Single-Page Applications und basiert grundsätzlich auf TypeScript. Angular läuft Clientseitig, aber wird meist mit einem Backend (z.B. einer REST API) verknüpft um so, effiziente und nutzerfreundliche Web-Apps zu bauen. Angular 13.2.0 ist die neuste Version des Nachfolgers von AngularJS.

Mit Angular lassen sich dynamische Apps und User-Interfaces relativ simpel erstellen, aber trotzdem bietet Angular viele advanced Features, wie einen eingebauten HTTP-Client, RxJS (asynchronous), oder einen eigenen Router.
Die Sprache der Wahl ist TypeScript, wobei die Verwendung der TypeScript-eigenen Features optional ist und auch reines JavaScript verwendet werden kann. Auch wenn Angular dem Entwickler viel Arbeit abnimmt, muss man dennoch viel lernen, da Angular ein großes und umfangreiches Framework bleibt.

Wie die meisten großen Frontend-Frameworks (vgl. Vue oder React) basiert Angular auf Komponenten. Komponenten sind Teile der UI, die in ein Template (HTML), die Logik (TypeScript) und die Styles (CSS) aufgeteilt werden. Sie sind wiederverwendbar und werden mit eigens deklarierten XML-Tags in die HTML Templates eingebunden. Mehr dazu im Abschnitt "Komponenten".

Um einen möglichst angenehmen Einstieg in Angular zu haben ist es vorteilhaft, wenn man schon grundlegende JavaScript (oder sogar TypeScript) Kenntnisse vorweisen kann, allerdings möchte ich hier nichts voraussetzen. Ich würde behaupten, wenn man schon einmal mit einer "C ähnlichen" Sprache gearbeitet hat, sollte man keine Probleme mit den angeführten Code-Beispielen haben.

CLI und Setup

Angular kommt mit seinem eigenen CLI (Command-Line-Interface) daher. Dieses wird genutzt um neue Projekte anzulegen, aber auch Komponenten und Services zu erzeugen und einen lokalen Dev-Server zu starten (der nicht für die Produktion verwendet werden sollte). Vorraussetzung hierfür ist, dass Node auf dem System installiert ist, da npm (der Node-Package-Manager) verwendet wird um Angular zu installieren.

npm install -g @angular/cli

Mithilfe der CLI lässt sich nun sehr simpel ein neues Projekt erstellen.

ng new -my-app --no-strict

Die Option "--no-strict" ist optional und beschreibt nur, ob die TypeScript Dateien "strenger" kompiliert werden (also die Typangaben strenger durchgesetzt werden). Nun kann man noch wählen ob man Angular-Routing direkt aktivieren möchte und welche Sprache man für die Syles verwenden möchte (CSS, SCSS ...).

Um den Dev-Server zu starten verwendet man folgenden Command:

ng serve

Nun wird die Angular-App via localhost:4200 bereitgestellt.

Ordnerstruktur

Nach dem Erstellen der ersten App kann die Anzahl der generierten Dateien erst einmal einschüchtern sein. Angular erstellt neben den Source-Files z.B auch Dateien zum tracken der npm-Abhängigkeiten in der "package.json" Datei, Angular-spezifische Konfigurationen in der "angular.json" Datei und Type-Script Konfigurationen in der "tsconfig.json".

Der interessantest Ordner ist allerdings der "src" Ordner. Im Ordner "/src/app" werden alle Komponenten und Services, und somit der gesamte Kern der App, Platz finden. Innerhalb des "src" Ordners existiert eine "index.html" Datei, welche der Ausgangspunkt der Angular-App ist. In dieser Datei wird, mit dem Tag <app-root>, die Root-Komponente der Angular App eingebunden, und von hier aus schachteln sich nun alle Komponenten, die man in der App einbringt. Natürlich kann man innerhalb dieser Datei auch einige Metadaten (z.B. den Titel) verändern.

Die "main.ts" Datei ist der Einstiegspunkt in Angular für die App. Hier wird z.B. das "AppModule" geladen. Man kann sich dieses "Module" aber wie eine Art Hub für die App vorstellen. Es ist der Zentrale Punkt der App, in den man weitere Module von Angular einbinden kann, um diese dann in seinem Code zu verwenden. Die Datei befindet sich im Order "/src/app", und in ihr wird vorerst nur der Kern von Angular geladen, aber später können weitere Module und auch Services importiert werden.

Desweiteren fallen bestimmt die vielen Dateien mit ".component" im Namen auf, welche mit den verschiedensten Dateiendungen enden (.css, .html, und .ts). Mit diesen Dateien wird eine Komponente beschrieben. Die Konvention für die Namensgebung lautet hier "<Komponentenname>.component.<Dateityp>". Wirklich viel muss man hier nicht beachten, da das CLI diese Dateien beim Erstellen neuer Komponenten automatisch nach diesem Schema benennt. Diese verschiedenen Dateien machen jetzt eine Komponente aus, welche in Logik, Template, und Styles aufgeteilt wird. Es existiert auch eine ".spec.ts" Datei welche für Tests eine Rolle spielt, aber diese soll an dieser Stelle erst einmal vernachlässigt werden.

Komponenten

Wie in der Einführung bereits beschrieben ist Angular komponentenbasiert. Eine Komponente ist ein in sich geschlossener Teil der UI, allerdings können Komponenten natürlich auch verschachtelt werden und sind vollkommen wiederverwendbar. Eine Komponente besteht aus einem Template (HTML), der Logik (TypeScript) und den optionalen Styles (z.B. CSS oder SCSS). Die Syntax einer Komponente sieht wie folgt aus:

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

@Component({
    selector: 'app-list',
    templateUrl: './list.component.html',
    providers:  [listService]
})

export class ListComponent implements OnInit{
    /* ... */

Begonnen wird mit einem Decorator, in welchem wichtige Rahmenbedingungen für die Komponente festgelegt werden wie z.B. der Selektor, also der Tag, mit dem die Komponente später im HTML Code verwendert werden kann. Auch der Pfad zum HTML-Template der Komponente wird hier festgelegt und das wäre auch genauso mit den Styles. Man muss erwähnen, dass Template und Styles auch optional inline direkt im Decorator definiert werden können. Davon ist aus Gründen der modularität aber eher abzuraten. Unter dem Keyword Providers werden die Services, welche verwendet werden aufgeführt. Dazu später mehr.

Auf den Decorator folgt eine relativ normale Klassendefinition. Innerhalb dieser Klasse werden die Methoden und Attribute der Komponente definiert.

Eine neue Komponente kann entweder "per Hand" erzeugt werden oder man verwendet das CLI:

 ng generate component <Komponentenname>

Angular kümmert sich hier direkt darum, dass die neue Komponente direkt in der "app.module.ts" Datei eingetragen wird und alle nötigen Imports stattfinden. Optional kann ein Pfad zur Komponente (ausgehend vom Ordner "src") angegeben werden, unter welchem die Komponente zu finden sein soll. Alle Unterordner zu diesem Pfad werden falls nötig automatisch erstellt. So lassen sich Komponenten logisch verschachteln und steigern so die Übersichtlichkeit.

In dieser neu erstellten Komponente kann man nun das Template, die Styles, als auch die Logik definieren. Werfen wir einmal einen Blick auf die "app-root" Komponente:

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

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls:['./app.component.css']
})

export class AppComponent implements OnInit{
    title:string = 'angular-app';
    
    ngOnInit(): void{
    }
}

Hier wird zusätzlich zum Template-File auch die CSS-Datei für die Styles angegeben. Durch den angegebenen Selektor kann die Root-Komponente nun in der "index.html" mit genau diesem Tag verwendet werden.

Die Methode "ngOnInit()" ist eine sogenannte Lifecycle-Methode. Im Fall von "ngOnInit()" wird diese Methode also dann ausgeführt, wenn die Komponente initiiert wird. Eine weitere Lifecycle-Methode wäre "ngOnDestroy()".

Attribute und Interpolation

Im oben gezeigten Code der "app-root" Komponente ist der "title" ein Attribut dieser Klasse bzw. dieser Komponente. Eine sehr wichtige Funktionalität von Angular ist es nun, dieses Attribut dynamisch in das HTML-Template einzubinden, um so auch Variablen aus der Logik im Browser anzeigen zu können. Das ganze wird möglich durch "String-Interpolation".

Der HTML-Code zum Anzeigen des "title" Attributes sieht so aus:

<h1> {{title}} </h1>

Durch zwei Paar geschweifte Klammern "{{}}" wird deutlich gemacht, dass hier eine TypeScript Expression, welche am Ende einen String liefern muss, ausgeführt wird. Im Browser wird jetzt natürlich nicht "{{title}}" angezeigt sondern der Wert des "title" Attributes in der Komponente, also in diesem Fall "angular-app". Prinzipiell kann jede Art von TypeScript Expression zwischen die beiden geschweiften Klammerpaare geschrieben werden.

Inputs

Bisher können wir Daten aus den Komponenten im Browser anzeigen lassen. Allerdings ist es auch sehr wichtig Daten in eine Komponente übergeben zu können. Hierfür sind Inputs zu verwenden. Sehen wir uns als Beispiel eine Button-Komponente an, in die man eine Frabe und einen Text quasi wie zwei Parameter übergeben kann:

<header> 
    <app-button 
        [color]="green" 
        [text]="Add">
    </app-button>
</header>

Das ganze nennt man "Databinding" ("[]" = Input) oder genauer "Propertybinding". Doch ganz so einfach ist es natürlich nicht. Wir müssen der Komponente noch sagen, dass sie diese "Parameter" überhaupt entgegennimmt und wie diese dann verwendet werden. Wie die Werte verwendet werden kommt natürlich auf den Anwendungsfall an. In diesem Abschnitt gehe ich nur auf die Integration solcher Inputs in der Komponente ein. Der Code hierzu sieht wie folgt aus:

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

@Component({
    selector: 'app-button',
    templateUrl: './button.component.html',
    styleUrls:['./button.component.css']
})

export class ButtonComponent implements OnInit{
    @Input text: string;
    @Input color: string;
    
    constructor(){}
    
    ngOnInit(): void{
    }
}

Nun können die übergebenen Daten in der Komponente verwendet werden.

Outputs und Events

Wenn es Inputs gibt muss es natürlich auch Outputs geben. Die einfachste Form eines Outputs ist ein Event im DOM. Angular bietet eine sehr einfache Möglichkeit auf Standard-Events zu reagieren:

<button
    class="btn"
    (click)="onClick()">Text
</button>

Auch hier spricht man von "Databinding", allerdings geht es hier in die andere Richtung ("()" = Output) man spricht eher von "Eventbinding". Innerhalb der Klammern wird das Event beschrieben, auf welches reagiert werden soll, in diesem Fall ein einfacher Klick. Natürlich könnte der Methode hier auch etwas übergeben werden. Darauf folgt die Methode der Komponente, welche daraufhin ausgeführt werden soll. Innerhalb der Komponente, die zu diesem Template gehört muss nun also die Methode "onClick()" realisiert werden:

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

@Component({
    selector: 'app-button',
    templateUrl: './button.component.html',
    styleUrls:['./button.component.css']
})

export class ButtonComponent implements OnInit{
    @Input text: string;
    @Input color: string;
    
    constructor(){}
    
    ngOnInit(): void{
    }
    
    onClick(){
        console.log("Click!");
    }
}

Wenn diese Komponente eingebunden wird und der Button geklickt wird, sollte man nun in der Konsole ein "Click!" sehen.

So viel zu Standard-Events. Nun wird es aber nötig sein eigene Events, also eigene Output-Signale, weiterzugeben. Auch das ist mit Angular relativ simpel möglich. Hierzu erstellt man ein Attribut vom Typ "EventEmitter", welches dann ein Event ausgeben kann:

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

@Component({
    selector: 'app-button',
    templateUrl: './button.component.html',
    styleUrls:['./button.component.css']
})

export class ButtonComponent implements OnInit{
    @Input() text: string;
    @Input() color: string;
    @Output() btnClick = new EventEmitter();
    
    constructor(){}
    
    ngOnInit(): void{
    }
    
    onClick(){
        this.btnClick.emit();
    }
}

Das angelegte EventEmitter Attribut wird nun in der "onClick()" Methode "aktiviert" und sendet ein Event an seine Parent-Komponente. Innerhalb der Parent-Komponente kann man dieses Event nun wieder wie bei Standard-Events abfangen:

<header> 
    <app-button 
        color="green" 
        text="Add"
        (btnClick)="printSomething()">
    </app-button>
</header>

Nun wird also indirekt "printSomething()" dieser Parent-Komponente aufgerufen wenn der Button geklickt wird. Dabei geht erstmal ein "click" Event an die Button-Komponente, welche das eigens erstelle Event "btnClick" auslöst, welches dann die Methode auslöst.

Modelle

Innerhalb von Angular ist es häufig eine gute Idee ein Modell von einem behandelten Datenobjekt zu erstellen. Ein einfaches Beispiel hierfür wäre eine To-Do Liste. Ein wiederkehrendes Element für welches ein Datenmodell sinnvoll ist wäre hier eine Aufgabe. Modelle werden typischerweise im "app" Ordner erstellt und nach dem Schema "<name>.model.ts" benannt. Prinzipiell ist ein Modell einfach nur eine Klasse, jedoch wird es in TypeScript als "Interface" bezeichnet. Es folgt ein Beispiel:

export interface Task {
  id?: number;
  text: string;
  day: string;
  reminder: boolean;
}

Dieses Modell kann nun in anderen Teilen der Applikation verwendet werden. Beispielweise für Services, welche mit Aufgaben arbeiten, oder der HTTP-Client welcher Aufgaben aus dem Backend laden wird.

Directives

Directives sind "Logik-Bausteine" welche teilweise mit Angular ausgeliefert werden, aber auch selbst definiert werden können. Mit ihnen kann das Aussehen, oder das Verhalten von Elementen im DOM (Document Object Model) verändert werden. Directives können dann in Elementen eingebaut und mit diesen verwendet werden. Nun folgen einige Beispiele:

Beginnen wir mit "ngStyle". Hiermit können inline CSS Regeln definiert werden, welche aber auch mit Attributen aus der Komponente (aus der TypeScript-Klasse) gefüttert werden können:

<button 
    [ngStyle]={'background-color': 'green'} 
    class="btn"> Text
</button>

So wird der Hintergrund des Buttons grün dargestellt.

Eine ähnliche Directive, wie "ngStyle" ist "ngClass". Mithilfe von "ngClass" kann einem Element dynamisch eine spezielle Klasse zugewiesen werden, um so z.B. spezielle CSS-Regeln auf dieses Element anzuwenden. Es folgt ein Beispiel:

<div [ngClass]="{toggled: data.isToggled}">
    ...
</div>

In diesem Beispiel wird dem "div"-Element die Klasse "toggled" zugewiesen, falls der Wert "isToggled" des Objektes "data" auf "true" steht.

Ein weiterer wichtiger Baustein, der direkt mit Angular daherkommt, ist "ngFor". Wie der Name schon vermuten lässt, handelt es sich hierbei um eine Art For-Schleife, mit der eine Komponente für z.B. mehrere Elemente eines Arrays erzeugt werden kann. ngFor ist eine Strukturelle Directive und das wird mit einem vorangestellten "" gekennzeichnet:

<p *ngFor="let task of tasks">
    {{task.name}}
</p>

Hier würde durch das Array "tasks" iteriert werden, und für jeden Task ein p-Tag angelegt werden, in welchem der Name des Tasks ausgegeben wird.

Die nächste wichtige strukturelle Directive wäre "*ngIf". Auch hier ist der Name Programm. Mit "*ngIf" lassen sich gewisse Elemente in Abhängigkeit zu einer Bedingung anzeigen, oder eben auch nicht.

<p *ngIf="dummyBoolean">
    {{task.name}}
</p>

In diesem Fall wird der Paragraph also angezeigt wenn "dummyBoolean" den Wert "true" enthält. Innerhalb der Anführungszeichen kann aber auch jede andere Expression stehen, solange diese am Ende entweder "true" oder "false" ergibt.

Services

Services werden strikt von Komponenten getrennt, um die Modularität und Wiederverwendbarkeit des Codes zu verbessern. Durch diese Aufteilung werden die Komponenten um einiges aufgeräumter, da man die Funktionalität die direkt mit der Ansicht zu tun hat, von anderen Prozessen "im Hintergrund" trennt. So kann eine Komponente dann spezielle Aufgaben an verschiedenste Services deligieren.

Services können ebenfalls durch das CLI erstellt werden:

ng generate service <service-name>

Auch hier gilt: Optional kann ein Pfad zum Service (ausgehend vom Ordner "src") angegeben werden, unter welchem dieser zu finden sein soll. Alle Unterordner zu diesem Pfad werden falls nötig automatisch erstellt.

Ein Service besteht aus einer TypeScript Datei, welche nach dem Schema <name>.service.ts benannt ist. Diese Datei sieht typischerweise so aus:

import { Injectable } from '@angular/core';
    
@Injectable({
    provideIn:'root'
})    

export class DummyService {
    
    constructor() { }
    
    dummyMethod():string{
        return "dummy";
    }
}

Dieser Service kann natürlich erstmal nicht viel. Doch wie bindet man ihn jetzt ein?

import { Componentn} from '@angular/core';
import {DummyService} from '../../services/task.service';

@Component({
    selector: 'app-button',
    templateUrl: './dummy.component.html',
    styleUrls:['./dummy.component.css']
})

export class Dummy implements OnInit{
    dummy_atribute:string;
    
    constructor(private dService: DummyService){}
    
    ngOnInit(): void{
        dummy_atribute = this.dService.dummyMethod();
    }
}

Hier passiert auf den ersten Blick sehr viel. Zuerst wird der Service importiert, um dann in die Komponente eingebunden zu werden. Das passiert als Parameter innerhalb des Konstruktors der Komponente. Von nun an exisitert in der "Dummy" Komponente eine Instanz vom DummyService mit dem Namen "dService". Diese Instant wird in der "onInit()" Methode verwendet um eine Methode, welche der Service bereitstellt zu verwenden.

Ein sehr gutes und auch weit verbreitetes Beispiel für einen Service, ist die Anbindung an ein Backend, um Daten von diesem zu beziehen, oder sie bereitzustellen. Im Abschnitt zum HTTP-Client wird so ein Service allgemein umschrieben.

Observables

Im Zusammenhang mit HTTP Requests lässt der Begriff "Asynchrone Kommunikation" natürlich nicht lange auf sich warten. Angular soll ja Clientseitig weiter funktionieren während es auf eine Antwort auf einen Request wartet. Hierfür stehen unter Angular sogenannte "Observables" bereit. Ein Observable ist quasi ein Objekt welches beobachtet ("observed") werden kann, und Rückmeldungen weitergeben kann, sobald diese eintriffen. Angular verwendet hier die rxjs-Bibliothek. Wie genau das aussehen kann zeige ich am Beispiel des DummyServices:

import { Injectable } from '@angular/core';
import {Observable, of} from 'rxjs';
    
@Injectable({
    provideIn:'root'
})    

export class DummyService {
    
    constructor() { }
    
    dummyMethod(): Observable<string>{
        const data = of("dummy");
        return data;
    }
}

Nun liefert die dummy-Methode ein Observable. Man spricht ab jetzt davon, dass man ein Observable "abonniert" sodass man es durchgehen überwachen kann. Das sieht in der Komponente so aus:

import { Componentn} from '@angular/core';
import {DummyService} from '../../services/task.service';

@Component({
    selector: 'app-button',
    templateUrl: './dummy.component.html',
    styleUrls:['./dummy.component.css']
})

export class Dummy implements OnInit{
    dummy_atribute:string;
    
    constructor(private dService: DummyService){}
    
    ngOnInit(): void{
        this.dummyService.dummyMethod().subscribe(
            (data) => (this.dummy_atribute = data);
        );
    }
}

Man kann sich das abonnieren (subscribe) ein wenig so vorstellen wie ein "Promise" mit ".then()". Innerhalb des Aufrufes der "subscribe" Methode wird mit einer Arrow-Function angegeben, was mit den übergebenen Daten (hier "data") passieren soll.

Subjects und Subscriptions

Eine andere Form von Observables sind Subjects. Ein Subject ist ein einzelnes Element, welches als Quell-Observable bezeichnet werden kann. Nun kann es ein oder mehrere Ziele geben, welches die Quelle durchgehend beobachten (eine/mehrere Subscription/s). Ein Subject wird häufig in einem Service abgebildet:

import { Injectable } from '@angular/core';
import {Observable, Subject} from 'rxjs';
    
@Injectable({
    provideIn:'root'
})    

export class SubjectService {
    private dummy_data: boolean = false;
    private subject = new Subject<any>();
    
    constructor() { }
    
    toggleDummyData():void {
        this.dummy_data = !this.dummy_data;
        this.subject.next(this.dummy_data);
    }
    
    onToggle(): Observable<any> {
        return this.subject.asObservable();
    }
}

Die Methode "toggleDummyData" im Beispiel, beschreibt eine Methode, die aufgerufen wird, sobald Daten verändert werden sollen. "onToggle()" wäre die Methode die nun von mehreren Zielen abonniert werden kann, damit diese Ziele auf Änderungen reagieren können:

import { Componentn} from '@angular/core';
import {SubjectService} from '../../services/subject.service';
import {Subscription} from 'rxjs';

@Component({
    selector: 'app-button',
    templateUrl: './dummy.component.html',
    styleUrls:['./dummy.component.css']
})

export class Dummy implements OnInit{
    dummy_atribute:boolean;
    subscription: Subscription;
    
    constructor(private sService: SubjectService){
        this.subscription = this.sService.onToggle().subscribe(
            (value) => (this.dummy_attribute = value)
        );
    }
    
    ngOnInit(): void{
        
    }
    
    toggleDummy(){
        this.sService.toggleDummyData();
    }
}

In diesem Beispiel befinden sich der Auslöser für Änderungen in den Daten, also die Methode "toggleDummy()" und die Reaktion auf die Änderungen, also die Subscription, in der selben Komponente. Nun ist es essenziell zu verstehen, dass so eine Reaktion/Subscription von überall aus stattfinden kann. Man hat also eine Quelle und 1-n Empfänger, die auf die Quelle reagieren können. Die eigentliche Subscription funktioniert prinzipiell wie ein einfacher aufruf eines Observables mittels ".subscribe()" und einer Arrow-Function als Parameter.

HTTP Client

Wie bereits erwähnt spielen Services und Observables vor allem in Zusammenhang mit HTTP-Requests eine Rolle. Angular kommt, in Kontrast zu anderen Frameworks, mit einem eingebauten HTTP-Client daher, welcher außerdem automatisch immer Daten vom Typ "Observable" zurückgibt. Ich beschreibe das ganze hier weiter anhand des DummyServices und unter der Annahme, dass ein voll funktionsfähiges Backend zur Verfügung steht. Zu Beginn ist es wichtig den Client in den Service zu importieren:

import { HttpClient, HttpHeaders } from '@angular/common/http';

Doch damit das überhaupt funktionieren kann ist es noch nötig den Client als Modul zur Applikation hinzuzufügen. Das ganze macht man, indem man in der Datei "app.module.ts" folgenden Import vornimmt:

import { HttpClientModule } from '@angular/common/http';

Außerdem muss man das Modul noch zum "imports"-Array hinzufügen:

imports:[
    BrowserModule,
    HttpClientModule
],

Nun folgt ein vollständiges Beispiel für einen GET-Request an ein Backend innerhalb des DummyServices:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import {Observable, of} from 'rxjs';
    
@Injectable({
    provideIn:'root'
})    

export class DummyService {
    private apiUrl = 'http://myBackend:5000/data';
    
    constructor(private http:HttpClient) { }
    
    dummyGET(): Observable<string>{
        return this.http.get<string>(this.apiUrl);
    }
}

Ähnlich wie bei einem eigens erstellten Service ist es hier nötig den Client in den Konstruktor einzubinden, um ihn dann verwenden zu können. Der eigentliche GET-Request ist relativ selbsterklärend. Interessanter wird es z.B. bei DELETE oder POST-Requests, da hier ggf. noch ID's und Header mit ins Spiel kommen. Dabei kommt es natürlich ganz auf die Funktionalität des Backends an. Hier also nochmal das Beispiel, aber diesmal mit einer Delete-Methode, welche die ID eines Datenelementes bennötigt, und mit einer Put- sowie Post-Methode, welche HTTP-Header verwenden:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import {Observable, of} from 'rxjs';
    
const httpOptions = {
    headers: new HttpHeaders({
        'Content-Type':'appliction/json'
    })
}
    
@Injectable({
    provideIn:'root'
})    

export class DummyService {
    private apiUrl = 'http://myBackend:5000/data';
    
    constructor(private http:HttpClient) { }
    
    dummyGET(): Observable<string>{
        return this.http.get<string>(this.apiUrl);
    }
    
    dummyDELETE(dummyData:data): Observable<data> {
        const url = `${this.apiUrl}/${data.id}`;
        return this.http.delete<data>(url);
    }
    
    dummyPUT(dummyData:data):Observable<data> {
         const url = `${this.apiUrl}/${data.id}`;
         return this.http.put<data>(url, data, httpOptions);
    }
    
    dummyPOST(dummyData:data):Observable<data> {
         return this.http.post<data>(this.apiUrl, task, httpOptions);
    }
}

Forms

In fast jeder Applikation wird es wichtig sein, mit Web-Forms zu interagieren. Egal ob Textfeld, Radiobuttons, oder Dropdowns, User-Input wird so gut wie immer gefragt sein. Auch hier bietet Angular viele Funktionen, um den Umgang mit Forms möglichst angenehm zu gestalten. Schauen wir uns ein sehr einfaches Form an:

<form>
    <div class="form-control">
        <label for="dummy">Dummy Textbox</label>
         <input 
            type="text" 
            name="dummy" 
            id="dummy"
        />
        <input 
            type="submit"
            value="submit"
        />
    </div>
</form>

Wenn man jetzt ein Attribut der Komponente, in der dieses Form exisitert, mit einem Input-Feld des Forms beidseitig verknüpfen möchte ("Two-way Databinding) bietet Angular hier das "ngModel". Mit "ngModel" kann so ein "Two-way Databinding" erzielt werden. Ein Attribut der Komponente wird bidirektional mit einem Input-Element verknüpft. Dafür muss die Komponente natürlich ein Attribut für jedes Input-Element bereithalten. Um die Directive "ngModel" verwenden zu können muss die Datei "app.module.ts" noch angepasst werden. Es muss, wie beim HttpClient, ein weiteres Modul zur Applikation hinzugefügt werden:

import { FormsModule } from '@angular/forms';

Außerdem muss man das Modul noch zum "imports"-Array hinzufügen:

imports:[
    BrowserModule,
    HttpClientModule,
    FormsModule
],

Innerhalb des HTML-Templates sieht die verwendung von "ngModel" wie folgt aus:

<form>
    <div class="form-control">
        <label for="dummy">Dummy Textbox</label>
        <input
            type="text" 
            name="dummy"
            [(ngModel)]="dummy_attribute"
            id="dummy"
        />
        <input 
            type="submit"
            value="submit"
        />
    </div>
</form>

Mit Hilfe von ngModel ("[()]" = Input+Output) wird die Textbox "dummy" nun mit dem Attribut "dummy_attribute" verknüpft.

Auch für das Abschicken von Forms (submit) bietet Angular seine eigene Funktionalität.

<form (ngSubmit)="onSubmit()">
    <div class="form-control">
        <label for="dummy">Dummy Textbox</label>
        <input
            type="text" 
            name="dummy"
            [(ngModel)]="dummy_attribute"
            id="dummy"
        />
        <input 
            type="submit"
            value="submit"
        />
    </div>
</form>

Mit dem Angular-Event "ngSubmit" kann das Abschicken des Forms abgefangen und mit einer Custom-Methode behandelt werden.

Routing

Angular ist ein Framework für "Single-Page Applications". Das bedeutet natürlich nicht, dass man mit Angular immer nur eine einzelne Seite eine Applikation bauen kann. Es bedeutet vielmehr, dass im Browser immer nur eine Seite geöffnet bleibt, und das "routing" verschiedener Seiten quasi im Hintergrund unter Angular passiert. Prinzipiell wird erstmal die "app-root" Komponente geladen, und mit dieser dann alle Komponenten, welche in "app-root" verankert sind. Nun bietet Angular auch die Funktionalität, verschiedene Komponenten unter verschiedenen URL's bereitzustellen. So können unter "myDomain.com/" andere Komponenten geladen und angezeigt werden als unter "myDomain.com/about". Hierfür verwendet man den Angular Router.

Den Router aktiviert man entweder beim erstellen einer neuen App, oder man aktiviert ihn später manuell, indem man wieder eine kleine Änderung an der Datei "app.module.ts" vornimmt:

import { RouterModule, Routes } from '@angular/router';

Außerdem muss man das Modul noch zum "imports"-Array hinzufügen:

imports:[
    BrowserModule,
    HttpClientModule,
    FormsModule,
    RouterModule.forRoot(appRoutes)
],

Nun kann man auch schon seine Routes erzeugen. Das ganze passiert ebenfalls in der "app.module.ts" Datei, überhalb des Decorators "@NgModule". Hier einmal ein Beispiel für verschiedene Routen:

const appRoutes: Routes = [
    {path: '', component:DummyComponent},
    {path; 'about', component; AboutComponent}
]

Das ganze ist relativ selbsterklärend. Man verknüpft einfach einen spezifischen Pfad mit einer gewünschten Komponente, welche dann unter diesem Pfad erreichbar ist. Damit Angular weiß, wo genau der "Output" des Routers geladen und gerendert werden soll ist noch eine Modifikation an der Hauptkomponente, unter "app.component.html" nötig. Hier muss noch ein spezieller Tag eingefügt werden:

<router-outlet></router-outlet>

Jetzt werden in "app-root" immer die Komponenten, die zum derzeitigen Pfad passen, geladen und angezeigt.

Weiterführendes Material

Ich hoffe ich konnte mit dieser knappen Einführung einigen Leuten weiterhelfen, oder zumindest Interesse erwecken. Vermutlich ist es am wichtigsten, die hier erklärten Funktionen einfach mal selbst auszuprobieren, um sie richtig zu verstehen und sie mal vor Augen zu haben. Dafür möchte ich noch zwei Quellen verknüpfen, welche meiner Meinung nach den Umgang mit Angular sehr detailliert, aber auch verständlich erklären:

Vielen Dank für's lesen!