Zum Inhalt springen

Dieselbe App in Angular

Angular ist ein vollständiges Framework von Google mit Router, Formularen, einem Client für HTTP und einem Werkzeug für die Kommandozeile, wie das Kapitel Frameworks im Vergleich beschreibt. Komponenten sind Klassen mit dem Decorator @Component und einem Template, und Angular arbeitet durchgehend mit TypeScript. Diese Seite zeigt den Katalog mit Suche und Favoriten als zwei Komponenten in Angular.

Auch Angular darfst du laut Projektauftrag im Projekt verwenden. Es lohnt sich, wenn du es im Beruf brauchst oder ein grösseres Projekt mit festen Regeln bauen willst. Ein neues Projekt legst du nach npm install -g @angular/cli mit ng new an. Seit Version 22 erscheint einmal im Jahr eine neue Hauptversion, die in der Regel 24 Monate lang unterstützt wird. Prüf vor dem Start, welche Versionen von Node und TypeScript sie verlangt, Angular 22 braucht TypeScript 6.0. Die Tabelle im Abschnitt Übertragbarkeit ordnet die Begriffe von Vue und Angular einander zu.

Das Beispiel unter optional/angular/de braucht die CLI nicht. npm run build übersetzt die Komponenten mit dem Angular-Compiler ngc, der dabei auch die Templates mit den Typen prüft. Im Browser siehst du sie, wenn du die Dateien aus src/app in ein Projekt der CLI übernimmst.

Signals halten den Zustand, ähnlich wie ref in Vue. signal('') erzeugt einen Wert, den das Template mit query() liest und der Code mit set oder update ändert. computed leitet die Treffer davon ab. @for braucht mit track einen Schlüssel, und @empty zeigt einen Text, wenn die Liste leer ist.

catalog.ts
import { Component, computed, signal } from '@angular/core'
import items from '../../data/items.json'
import type { Item } from './item'
import { ItemCard } from './item-card'
// Signals hold the state, computed derives the hits as computed does in Vue
@Component({
selector: 'app-catalog',
imports: [ItemCard],
template: `
<main>
<h1>Katalog</h1>
<label for="search">Suchen</label>
<input #search id="search" type="search" [value]="query()" (input)="query.set(search.value)" />
<p role="status">{{ hits().length }} Dinge gefunden, {{ favorites().length }} Favoriten</p>
<ul class="grid">
@for (item of hits(); track item.id) {
<li>
<app-item-card [item]="item" [favorite]="favorites().includes(item.id)" (toggleFavorite)="toggleFavorite($event)" />
</li>
} @empty {
<li>Nichts gefunden.</li>
}
</ul>
</main>
`,
})
export class Catalog {
protected readonly query = signal('')
protected readonly favorites = signal<number[]>([])
protected readonly hits = computed(() => {
const text = this.query().trim().toLowerCase()
return (items as Item[]).filter((item) => item.name.toLowerCase().includes(text))
})
protected toggleFavorite(id: number) {
this.favorites.update((current) => (current.includes(id) ? current.filter((favorite) => favorite !== id) : [...current, id]))
}
}

Die Karte bekommt ihre Daten über input.required() und input(), und output() meldet den Klick, so wie defineProps und defineEmits in Vue. Seit Version 19 sind Komponenten standalone, die Karte steht deshalb im Array imports des Katalogs.

item-card.ts
import { Component, input, output } from '@angular/core'
import type { Item } from './item'
// Signal inputs take the place of props, an output the place of an emitted event
@Component({
selector: 'app-item-card',
template: `
<article class="card">
<h2>{{ item().name }}</h2>
<p>{{ item().category }}, CHF {{ item().deposit }} Pfand</p>
<button type="button" [attr.aria-pressed]="favorite()" (click)="toggleFavorite.emit(item().id)">
{{ favorite() ? 'Favorit entfernen' : 'Als Favorit merken' }}
</button>
</article>
`,
})
export class ItemCard {
readonly item = input.required<Item>()
readonly favorite = input(false)
readonly toggleFavorite = output<number>()
}

Der Compiler findet Fehler in den Templates. Fehlt beim Einsetzen der Karte das Input item, meldet ngc den Fehler NG8008, fehlt bei @for das track, den Fehler NG5002. Steht ein Signal im Template ohne Klammern, ist das ein Fehler bei den Typen, denn erst der Aufruf liefert den Wert.

Texte und Abbildungen stehen unter CC BY-SA 4.0, der Code unter der MIT-Lizenz und die Daten des Kurses unter CC0 1.0. Die Schrift der Abbildungen ist Kalam unter der SIL Open Font License 1.1. Die Quellen liegen auf GitHub.