How to Set Up Two-Way Binding in Angular
Bind an input to a signal in Angular by reading it with [value] and writing it in the (input) event, so the field and the state always mirror each other — with the [(ngModel)] shorthand as the alternative.
Overview
Two-way binding keeps an input and your state in lockstep: what the user types flows into state, and what is in state flows back into the field. With signals you build it from two one-way bindings — read the signal into the input’s value, and write it back on every change. The recommended approach is three small moves, and the [(ngModel)] shorthand is a fourth option once you have seen the parts.
Steps
1. Hold the value in a signal
Every bound field needs a piece of reactive state to live in. Declare it with signal(), giving it a sensible starting value — usually an empty string for a text field. This signal is now the single source of truth for what the field contains: the input will display it and update it, but the value itself lives here in state.
name = signal("");
2. Read the signal into the input with [value]
Bind the signal to the input by setting its value property to the signal read. This is the first half of two-way binding — the state-to-view direction. On the first render the field shows the empty string you initialised, and from then on the field always mirrors whatever the signal holds, because [value]="name()" re-renders whenever the signal changes.
<input [value]="name()" />
3. Write the input back into the signal in (input)
Now add the view-to-state direction. Listen for the (input) event and write the field’s current text into the signal with .set(), reading it from $event.target.value. Each keystroke fires the event, .set() stores the new value and notifies dependents, and the view stays in sync. Together, [value] reading out and (input) writing back are two-way binding built by hand.
<input
[value]="name()"
(input)="name.set($any($event.target).value)"
/>
Put together, the three moves live in one small standalone component: a signal holds the value, [value] reads it into the field, and (input) writes each keystroke back through .set().
import { Component, signal } from "@angular/core";
@Component({
selector: "app-root",
standalone: true,
template: `
<input [value]="name()" (input)="name.set($any($event.target).value)" />
<p>You typed: {{ name() }}</p>
`
})
export class App {
name = signal("");
}
Runnable example
Here are the three steps together — an input bound to a signal, whose typed value is echoed live below the field. Type in it and watch the text appear as you go, which is the state-to-view and view-to-state directions both working at once:
The echo under the input reads straight from the signal, which is why it updates on every keystroke. Open it in the editor and try adding a second field; the same pattern extends to any number of inputs, each with its own signal.
Angular also offers a shorthand. Import FormsModule and you can collapse the read and the write into a single banana-in-a-box binding, [(ngModel)], on a plain property. It reads and writes in one expression, which is convenient for template-driven forms, though it works on a component property rather than directly on a signal:
Common pitfalls
One more note: read the typed value from $event.target.value in the (input) handler, not from the signal — the signal has not been updated yet at that moment. The event carries the newest text; you write it into the signal, and the signal then drives the view.
Frequently asked questions
How do I do two-way binding with signals in Angular?
[value]="name()", and write it back on change with (input)="name.set($event.target.value)". The field and the signal then always mirror each other.What is the [(ngModel)] syntax in Angular?
[(ngModel)]="name". It requires FormsModule in the component’s imports and binds to a plain property rather than directly to a signal.Why is my Angular input not updating when I type?
[value] from a signal but forgot the (input) handler. With the value pinned to state and nothing writing back, the field is effectively read-only. Add (input)="name.set($event.target.value)".Do I need FormsModule for two-way binding?
[(ngModel)] shorthand, which needs FormsModule in the standalone component’s imports. The signal approach — [value] plus (input) — needs no extra module, since it uses ordinary property and event bindings.