Template-Syntax
Auftrag
Martina Hostettler möchte den Katalog in der neuen App sehen, so wie er in der Version von Hand aussah.
Als Mitglied möchte ich im Katalog alle Dinge mit Kategorie und Verfügbarkeit sehen, damit ich weiss, was ich ausleihen kann.
Die Akzeptanzkriterien lauten:
- Der Katalog zeigt jedes Ding mit Name und Kategorie.
- Verfügbare Dinge sind mit «Verfügbar» markiert, ausgeliehene mit dem Datum, ab dem sie wieder da sind, und sie sehen anders aus.
- Ein Klick auf den Namen zeigt das Pfand des Dings.
Lernziele
Nach diesem Kapitel kannst du
- Werte mit Interpolation anzeigen und Attribute mit
v-bindbinden, - Klassen und Stile abhängig von Daten setzen,
- Teile der Seite mit
v-ifundv-showbedingt anzeigen, - Listen mit
v-forundkeyrendern, - mit
v-onauf Events reagieren und Modifikatoren einsetzen.
Interpolation
Abschnitt betitelt „Interpolation“Das Template einer Komponente ist HTML mit einigen Zusätzen. Der einfachste ist die Interpolation mit doppelten geschweiften Klammern. Vue wertet den Ausdruck darin aus und setzt das Ergebnis als Text ein. Lies das Beispiel und überleg dir für jeden Absatz, was erscheint.
<script setup>const item = { name: 'Raclette-Ofen', category: 'Küche', deposit: 30, availableFrom: null }const note = '<b>Nur für Mitglieder</b>'</script>
<template> <h1>{{ item.name }}</h1> <!-- Every JavaScript expression works, a statement such as if does not --> <p>{{ item.category.toUpperCase() }}, Pfand CHF {{ item.deposit.toFixed(2) }}</p> <p>{{ item.availableFrom === null ? 'Verfügbar' : 'Ausgeliehen' }}</p> <!-- Interpolation inserts text, so the tags appear as characters --> <p>{{ note }}</p></template>Zwischen den Klammern steht ein JavaScript-Ausdruck, also etwas, das einen Wert ergibt. Aufrufe wie toUpperCase(), Rechnungen und der Operator ? : sind erlaubt, Anweisungen wie if, Schleifen oder const nicht. Für Längeres gibt es Funktionen im Script und die berechneten Werte aus dem nächsten Kapitel.
Der letzte Absatz zeigt die Tags von note als Zeichen. Vue setzt Interpolation immer als Text ein und erledigt damit das, wofür du in Teil 2 escapeHtml gebraucht hast. Die Direktive v-html setzt dagegen HTML ein, ohne es zu prüfen. Sie gehört nur zu Inhalten, die du selbst geschrieben hast, nie zu Daten von Mitgliedern oder aus einer API.
Attribute binden
Abschnitt betitelt „Attribute binden“Interpolation funktioniert nur im Text, nicht in Attributen. Ein Attribut bindest du mit der Direktive v-bind an einen Ausdruck, meist in der Kurzform mit einem Doppelpunkt. :href="'#/items/' + item.id" setzt das Attribut href auf das Ergebnis des Ausdrucks. Direktiven sind Attribute, die mit v- beginnen und Vue sagen, was es mit einem Element tun soll.
Bei Attributen wie disabled, die nur an oder aus sind, entfernt Vue das Attribut, wenn der Ausdruck false ergibt. :disabled="item.availableFrom !== null" sperrt den Knopf also nur bei ausgeliehenen Dingen.
<script setup>const drill = { id: 1, name: 'Bohrmaschine', deposit: 20, availableFrom: null }const tent = { id: 2, name: 'Zelt für vier Personen', deposit: 50, availableFrom: '2026-11-12' }</script>
<template> <!-- The colon binds an attribute to an expression, :href is short for v-bind:href --> <article class="card" :class="{ 'on-loan': drill.availableFrom !== null }"> <a :href="'#/items/' + drill.id">{{ drill.name }}</a> <button type="button" :disabled="drill.availableFrom !== null">Reservieren</button> </article> <article class="card" :class="{ 'on-loan': tent.availableFrom !== null }" :style="{ borderWidth: tent.deposit >= 50 ? '3px' : '1px' }"> <a :href="'#/items/' + tent.id">{{ tent.name }}</a> <button type="button" :disabled="tent.availableFrom !== null">Reservieren</button> </article></template>
<style>.card { display: flex; gap: 1rem; align-items: center; justify-content: space-between; margin-bottom: 0.5rem; padding: 0.5rem 1rem; border: 1px solid #c8c8c8; border-radius: 8px;}.on-loan { background: #f6e7e4;}</style>Klassen und Stile
Abschnitt betitelt „Klassen und Stile“Für Klassen hat :class eine eigene Schreibweise. Du übergibst ein Objekt, dessen Schlüssel Klassen sind, und Vue setzt jede Klasse, deren Wert wahr ist. :class="{ 'on-loan': drill.availableFrom !== null }" setzt die Klasse on-loan nur bei ausgeliehenen Dingen. Ein gewöhnliches Attribut class daneben bleibt erhalten, Vue fügt beides zusammen. Statt eines Objekts geht auch ein Array wie :class="['card', size]".
:style nimmt ein Objekt mit CSS-Eigenschaften in camelCase, etwa { borderWidth: '3px' }. Klassen sind meist die bessere Wahl, weil das Aussehen dann im CSS bleibt und sich an einer Stelle ändern lässt.
Bedingungen
Abschnitt betitelt „Bedingungen“v-if rendert ein Element nur, wenn der Ausdruck wahr ist. Mit v-else-if und v-else direkt dahinter entsteht eine Kette, von der Vue genau einen Teil anzeigt. Das passt zu den Zuständen beim Laden, die du aus Teil 2 kennst.
<script setup>// Try 'loading' and 'error' as wellconst status = 'ready'const itemCount = 60const showRules = false</script>
<template> <p v-if="status === 'loading'">Der Katalog wird geladen …</p> <p v-else-if="status === 'error'">Der Server ist nicht erreichbar.</p> <p v-else>{{ itemCount }} Dinge im Katalog</p> <!-- v-show only hides the element with CSS, it stays in the page --> <p v-show="showRules">Eine Ausleihe dauert höchstens sieben Tage.</p></template>v-show versteckt ein Element dagegen nur mit display: none, es bleibt in der Seite. Das lohnt sich für Teile, die oft auf- und zugehen. v-if passt, wenn ein Teil selten erscheint oder ein Element ganz fehlen soll. Willst du mehrere Elemente gemeinsam ein- und ausblenden, ohne ein zusätzliches Element in die Seite zu setzen, schreibst du v-if an ein <template>. Das Element <template> selbst rendert Vue nicht.
v-for rendert ein Element für jeden Eintrag eines Arrays. v-for="item in items" stellt den Eintrag unter dem Namen item bereit, v-for="(item, index) in items" zusätzlich seine Position.
<script setup>const items = [ { id: 1, name: 'Bohrmaschine', category: 'Werkzeug', availableFrom: null }, { id: 2, name: 'Zelt für vier Personen', category: 'Camping', availableFrom: '2026-11-12' }, { id: 3, name: 'Raclette-Ofen', category: 'Küche', availableFrom: null }, { id: 4, name: 'Festbankgarnitur', category: 'Feste', availableFrom: '2026-11-03' },]</script>
<template> <ul> <!-- The key tells Vue which entry belongs to which thing --> <li v-for="(item, index) in items" :key="item.id" :class="{ 'on-loan': item.availableFrom !== null }"> {{ index + 1 }}. {{ item.name }}, {{ item.category }} </li> </ul></template>
<style>.on-loan { color: #a3341f;}</style>Das Attribut :key sagt Vue, welches Element zu welchem Eintrag gehört. Ändert sich die Liste, etwa durch einen Filter oder eine neue Sortierung, verschiebt Vue die vorhandenen Elemente anhand des Schlüssels, statt sie der Reihe nach neu zu befüllen. Ohne Schlüssel kann ein Eingabefeld oder ein offenes Detail beim falschen Ding landen. Der Schlüssel muss eindeutig und beständig sein, die id eines Dings eignet sich, die Position index nicht, denn sie ändert sich beim Filtern.
Setze v-if und v-for nie an dasselbe Element. Vue wertet v-if zuerst aus, und die Variable der Schleife gibt es dann noch nicht. Für eine gefilterte Liste gibt es berechnete Werte aus dem nächsten Kapitel, oder du setzt v-for an ein umschliessendes <template>.
v-on reagiert auf Events, meist in der Kurzform mit @. @click="reserve" ruft bei einem Klick die Funktion reserve auf. Steht ein Ausdruck da, führt Vue ihn aus, etwa @click="count = 0" oder @click="toggle(item.id)". Das Event selbst steht dabei unter $event bereit.
Die Beispiele brauchen einen Wert, der sich ändern kann. Dafür steht ref, ein Wert, dem das Template folgt. Im Script liest und schreibst du ihn über .value, im Template ohne. Das nächste Kapitel erklärt, was dahintersteckt.
<script setup>import { ref } from 'vue'
// A ref holds a value that the template follows, the next chapter explains itconst count = ref(0)const message = ref('')
function reserve() { count.value++}</script>
<template> <button type="button" @click="reserve">Reservieren</button> <button type="button" @click="count = 0">Zurücksetzen</button> <p>Reservationen: {{ count }}</p> <!-- .prevent keeps the browser from following the link --> <a href="#/rules" @click.prevent="message = 'Die Ausleihregeln folgen bald.'">Ausleihregeln</a> <!-- .enter reacts to the Enter key only --> <p> <input aria-label="Notiz" @keyup.enter="message = $event.target.value"> </p> <p>{{ message }}</p></template>Modifikatoren hängen mit einem Punkt an der Direktive und erledigen häufige Aufgaben. .prevent ruft preventDefault() auf, der Link führt dann nirgends hin. .stop hält das Event auf, bevor es zu umgebenden Elementen gelangt, und .once reagiert nur auf das erste Event. Bei Tasten wählen Modifikatoren wie .enter oder .esc die Taste aus, @keyup.enter reagiert also nur auf die Eingabetaste.
Im Code
Abschnitt betitelt „Im Code“Arbeite mit dem Beispiel zur Liste. Lies den Code und schreib auf, was du erwartest, bevor du etwas änderst.
- Was zeigt die Liste, wenn du beim Raclette-Ofen
availableFromauf'2026-11-20'setzt? - Setz an das Element
lizusätzlichv-if="item.availableFrom === null". Was meldet der Playground, und warum? - Löse das Problem aus Frage 2 mit einem
<template v-for="item in items" :key="item.id">um das Elementli, das nur nochv-ifträgt. - Ändere
:key="item.id"in:key="index". Was siehst du, und warum fällt der Unterschied in diesem Beispiel nicht auf?
Aufgaben
Abschnitt betitelt „Aufgaben“Die Aufgaben bauen den Katalog aus dem Auftrag. Du findest das Projekt im Kursrepo im Ordner exercises/03-vue/template-syntax/de. Kopiere den Ordner, installiere die Pakete und starte die Tests.
npm installnpm testMit npm run dev siehst du den Katalog im Browser. Zu Beginn schlagen alle Tests fehl.
Aufgabe Einstieg
Zeig in App.vue alle Dinge als Liste <ul class="catalog">. Jeder Eintrag li enthält den Namen in einem Element mit der Klasse name und die Kategorie in einem Element mit der Klasse category. Gib jedem Eintrag die id als key.
Lösung
Die Liste entsteht mit <li v-for="item in items" :key="item.id">. Darin stehen <span class="name">{{ item.name }}</span> und <span class="category">{{ item.category }}</span>. Die Lösung der Knobelei macht aus dem Namen später einen Knopf, die Klasse name bleibt.
Aufgabe Vertiefung
Zeig bei jedem Ding in einem Element mit der Klasse state «Verfügbar» oder «Wieder verfügbar ab 12.11.2026». Das Datum formatiert formatDate aus src/format.js. Gib den Einträgen ausgeliehener Dinge die Klasse on-loan, das CSS färbt ihren Zustand dann rot.
Lösung
Die Klasse hängt am Element li.
<li v-for="item in items" :key="item.id" :class="{ 'on-loan': item.availableFrom !== null }">Für den Zustand stehen zwei Elemente mit v-if und v-else direkt hintereinander.
<span v-if="item.availableFrom === null" class="state">Verfügbar</span> <span v-else class="state">Wieder verfügbar ab {{ formatDate(item.availableFrom) }}</span>Damit formatDate im Template bereitsteht, importierst du es im Script. Alles, was <script setup> importiert, kann das Template verwenden.
Aufgabe Knobelei
Mach aus dem Namen einen Knopf button mit der Klasse name. Ein Klick darauf zeigt in einem Element mit der Klasse details das Pfand, etwa «Pfand CHF 80.00», ein zweiter Klick versteckt es wieder. Es ist immer höchstens ein Ding offen. Die Details stehen mit v-show in jedem Eintrag, und aria-expanded am Knopf sagt Screenreadern, ob sie offen sind. Den Zustand hält const openId = ref(null), das Pfand formatiert formatDeposit.
Lösung
<script setup>import { ref } from 'vue'
import items from '../data/items.json'import { formatDate, formatDeposit } from './format.js'
// The id of the thing whose details are open, null while all are closedconst openId = ref(null)
function toggle(id) { openId.value = openId.value === id ? null : id}</script>
<template> <h1>Katalog</h1> <p>{{ items.length }} Dinge</p> <ul class="catalog"> <li v-for="item in items" :key="item.id" :class="{ 'on-loan': item.availableFrom !== null }"> <button type="button" class="name" :aria-expanded="openId === item.id" @click="toggle(item.id)"> {{ item.name }} </button> <span class="category">{{ item.category }}</span> <span v-if="item.availableFrom === null" class="state">Verfügbar</span> <span v-else class="state">Wieder verfügbar ab {{ formatDate(item.availableFrom) }}</span> <p v-show="openId === item.id" class="details">Pfand {{ formatDeposit(item.deposit) }}</p> </li> </ul></template>openId merkt sich die id des offenen Dings. Weil es nur einen solchen Wert gibt, kann immer nur ein Ding offen sein. toggle schliesst die Details, wenn jemand ein zweites Mal auf denselben Namen klickt. :aria-expanded bekommt true oder false, und Vue schreibt den Wert als Text in das Attribut. Ein Knopf statt eines gewöhnlichen Texts lässt sich auch mit der Tastatur erreichen und auslösen.
Typische Fehler
Abschnitt betitelt „Typische Fehler“| Fehler | Was hilft |
|---|---|
href="{{ link }}" führt auf eine Adresse mit geschweiften Klammern. | In Attributen gibt es keine Interpolation. :href="link" schreiben. |
disabled="false" sperrt den Knopf trotzdem. | Ohne Doppelpunkt ist der Wert ein Text, und das Attribut ist gesetzt. :disabled="false" schreiben. |
Vue meldet v-else/v-else-if has no adjacent v-if or v-else-if. | v-else muss direkt auf das Element mit v-if folgen, ohne ein anderes Element dazwischen. |
| Nach dem Filtern stehen Eingaben oder offene Details beim falschen Ding. | Als key eine beständige id verwenden, nicht die Position. |
v-if und v-for am selben Element, die Variable der Schleife fehlt. | v-for an ein umschliessendes <template> setzen oder die Liste vorher filtern. |
Ein KI-Assistent zeigt Text aus den Daten mit v-html an. | Interpolation verwenden. v-html setzt HTML ungeprüft ein und öffnet die Tür für eingeschleusten Code. |
Selbstcheck
Abschnitt betitelt „Selbstcheck“Selbstcheck
- Worin unterscheidet sich
{{ note }}vonv-html="note"? - Was passiert bei
disabled="false"und was bei:disabled="false"? - Wann wählst du
v-showstattv-if? - Warum braucht
v-foreinenkey, und welcher Wert eignet sich dafür?
Antworten
- Interpolation setzt den Wert als Text ein, Tags erscheinen als Zeichen.
v-htmlsetzt ihn als HTML ein, ohne ihn zu prüfen. - Ohne Doppelpunkt ist
"false"ein Text, das Attribut ist gesetzt und der Knopf gesperrt. Mit Doppelpunkt ergibt der Ausdruckfalse, und Vue entfernt das Attribut. - Wenn ein Teil oft auf- und zugeht.
v-showversteckt das Element nur,v-ifentfernt es und baut es wieder auf. - Damit Vue beim Ändern der Liste die Elemente den richtigen Einträgen zuordnet. Geeignet ist ein eindeutiger, beständiger Wert wie die id.
Auftrag
Der Katalog steht, und am Tresen schauen schon die ersten Freiwilligen hinein. Mit 60 Dingen wird die Liste aber lang. «Kann man darin suchen und nach Kategorie filtern?», fragt Martina Hostettler. «Und am Tresen möchten wir sehen, wie viele Dinge gerade da sind.» Das nächste Kapitel macht den Katalog reaktiv.
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.