model() erzeugt ein Signal, das zugleich Input und Output ist. Die Komponente kann es beschreiben (set/update), und der Schreibzugriff erreicht den Parent automatisch über das Event nameChange. Das ist die Grundlage der Two-Way-Syntax [( )] („Banana in a Box“).
Beispiel
import { Component, input, model } from '@angular/core';
@Component({
selector: 'app-toggle',
template: `
<button role="switch" [attr.aria-checked]="checked()" (click)="checked.set(!checked())">
<span class="knob"></span>
</button>
{{ label() }}
`,
})
export class Toggle {
// model() = input + output „checkedChange”, in einem einzigen beschreibbaren Signal
readonly checked = model(false);
readonly label = input('');
}{
"notifications": false,
"darkMode": true,
"guests": 2,
"plan": "Free"
}model.required
@Component({ selector: 'app-stepper', /* ... */ })
export class Stepper {
readonly value = model.required<number>(); // der Parent MUSS einen Wert liefern
readonly min = input(0);
readonly max = input(10);
increment() {
this.value.update((v) => Math.min(v + 1, this.max())); // schreiben → benachrichtigt den Parent
}
}model() oder Input + Output?
// model(): Der Zustand „gehört“ beiden — das Control ändert ihn, der Parent kann ihn setzen
readonly selected = model<string>();
// input + output: Das Kind SCHLÄGT nur VOR, der Parent entscheidet (er kann ablehnen / validieren / auf dem Server speichern)
readonly value = input.required<number>();
readonly valueChange = output<number>();
// Parent: (valueChange)="confirmAndSave($event)"- „Wert“-Controls: Toggle, Slider, Stepper, Color Picker, Date Picker.
- UI-Zustand, den der Parent womöglich steuern will: aktiver Tab, aufgeklapptes Panel, geöffneter Dialog.
- Das einfache Gegenstück zu
ngModelfür eigene Komponenten.
- Der Parent muss die Änderung validieren oder bestätigen (z. B. beim Speichern auf dem Server).
- Events, die nicht „mein Wert hat sich geändert“ bedeuten:
(save),(delete). - Felder in einem Signal-Forms-Formular → implementiere
FormValueControl(siehe Custom Controls) — weiterhin mitmodel(), aber in die Validierung eingebunden.
Was model() wirklich erzeugt
Ein Feld checked = model(false) deklariert zwei Dinge an der Komponente: einen Input namens checked und einen Output namens checkedChange. Die Syntax [(checked)] ist nur die Kurzform für beides. Deshalb kann der Parent wählen: Two-Way, nur Input oder nur Output.
export class Toggle {
readonly checked = model(false);
// Was Angular an der Komponente sieht:
// input: checked
// output: checkedChange
}
<!-- dieselben zwei Bindungen, auf vier Arten geschrieben -->
<app-toggle [(checked)]="enabled" />
<app-toggle [checked]="enabled()" (checkedChange)="enabled.set($event)" />
<app-toggle [checked]="true" /> <!-- nur Input -->
<app-toggle (checkedChange)="log($event)" /> <!-- nur Output -->Ein Detail, das überrascht: Bindet der Parent einen festen Wert ([checked]="true"), kann das Kind seinen Wert trotzdem mit set() ändern. Die Änderung bleibt lokal, bis der Parent einen neuen Wert schickt, der sie überschreibt. Soll der Parent die einzige Quelle der Wahrheit sein, binde ein Signal mit [( )].
Objekte und Listen in model()
Wie jedes Signal meldet model() eine Änderung nur bei einer neuen Referenz. Änderst du ein Feld des Objekts direkt, erfahren weder Parent noch Template davon. Schreibe immer eine Kopie, mit update().
export class AddressEditor {
readonly address = model<Address>({ street: '', city: '' });
setCity(city: string) {
// ✗ Mutation: niemand erfährt davon, der Parent behält den alten Wert
this.address().city = city;
// ✓ neue Referenz: Template und Parent werden aktualisiert
this.address.update((a) => ({ ...a, city }));
}
}Die Optionen von model()
model() hat nur zwei Optionen: alias (der öffentliche Name; der Output heißt dann aliasChange) und debugName (für Angular DevTools). Ein transform wie bei input() gibt es nicht. Musst du den eingehenden Wert umwandeln, etwa einen String aus dem HTML in eine Zahl, nimm input() mit transform plus einen eigenen output().
Häufige Stolperfallen
[(checked)]="notifications()"— mit Klammern — ist kein Two-Way-Binding: Du bindest einen Wert, nicht das Signal. Schreibe[(checked)]="notifications".- Ein schreibgeschütztes Signal im Parent (ein
computed) in[( )]→ Compile-Fehler. Two-Way braucht einWritableSignaloder eine einfache Property. - Zwei „Besitzer“ des Zustands: Wenn sowohl der Parent als auch das Kind Logik auf denselben Wert anwenden, wird es schwer nachvollziehbar. Lege fest, wer die Source of Truth ist.
Spickzettel
model<T>() // ModelSignal<T | undefined>
model(default) // ModelSignal<T>
model.required<T>()
m.set(v); m.update(fn); // schreibt lokal + emittiert `${name}Change`
<app-x [(value)]="sig" /> // Two-Way auf einem Signal
<app-x [value]="v" (valueChange)="onChange($event)" />