Two-Way Binding
Two-way binding keeps a value in your class and a value on screen in sync in both directions — type in an input and the class updates; change the class and the input updates.
On this page · 5 min · Intermediate
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 5 min
- Difficulty
- Intermediate
- Updated
- Oct 28, 2026
Learning objectives#
- Use [(ngModel)] with FormsModule
- Bind to signals with two-way binding
- Create a two-way bindable component with model()
- Understand the property + event pair behind [( )]
Explanation#
[(x)]="value" is shorthand for [x]="value" (x)="value = $event" — data in and events out together.
For form fields, import FormsModule and write <input [(ngModel)]="name" />. Two-way binding also works directly with a writable signal: [(ngModel)]="name" where name = signal("").
Your own components support two-way binding with model(): value = model(0). The parent writes <app-stepper [(value)]="qty" /> and both stay in sync.
Inside the child, this.value.set(...) updates its own value and notifies the parent automatically.
Use two-way binding for simple editors. For complex forms with validation, reactive forms give more control.
Syntax#
<input [(ngModel)]="name" /> <!-- form field -->
<app-stepper [(value)]="quantity" /> <!-- your component with model() -->
Code example: A stepper component and a text field, both two-way#
import { Component, computed, model, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-stepper',
template: `
<button (click)="decrease()" [disabled]="value() <= min">−</button>
<strong> {{ value() }} </strong>
<button (click)="increase()">+</button>
`,
})
export class Stepper {
value = model(1); // two-way bindable input
min = 1;
increase() { this.value.update(v => v + 1); }
decrease() { this.value.update(v => Math.max(this.min, v - 1)); }
}
@Component({
selector: 'app-cart-line',
imports: [FormsModule, Stepper],
template: `
<label>Gift message <input [(ngModel)]="message" maxlength="40" /></label>
<p>Preview: “{{ message() || '…' }}”</p>
<app-stepper [(value)]="quantity" />
<p>{{ quantity() }} × ₹60 = ₹{{ total() }}</p>
<button (click)="quantity.set(1)">Reset quantity</button>
`,
})
export class CartLine {
message = signal('');
quantity = signal(2);
total = computed(() => this.quantity() * 60);
}
Typing in the gift-message box updates the preview instantly. The stepper starts at 2 and the line reads “2 × ₹60 = ₹120”; + and − change both the stepper and the total. “Reset quantity” sets the parent signal to 1 and the stepper shows 1 — data flows both ways.
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- model(1) declares a value the parent can bind with [( )].
- The stepper changes its own value with update; Angular pushes the change to the parent’s quantity signal.
- [(value)]="quantity" in the parent connects the stepper to the quantity signal.
- [(ngModel)]="message" binds the input to a signal; FormsModule must be imported.
- Resetting quantity in the parent updates the stepper — proving the binding works in both directions.
Real-world use case#
A product configurator uses <app-colour-picker [(colour)]="colour"> and <app-size-picker [(size)]="size">; the preview image and price react to both instantly.
Common mistakes#
FormsModule is missing from the component’s imports.
Two-way binding needs something writable — a property or a writable signal, not a computed().
Bidirectional data flow is harder to follow. Use input()/output() when the child should only notify.
Best practices#
- model() for reusable editable components
- Bind writable signals
- Use reactive forms for complex validation
- Keep data flow mostly one-way
Remember this#
[( )] = [value] + (valueChange).
Form fields: [(ngModel)] + FormsModule.
Own components: value = model().
Practice exercise#
Your task: Create a ToggleSwitch component with an on = model(false) value and a button that flips it, and use it two-way from a parent that shows "Notifications: ON/OFF".
Need a hint?
Show solution
import { Component, model, signal } from '@angular/core';
@Component({
selector: 'app-toggle',
template: `<button (click)="flip()" [attr.aria-pressed]="on()">{{ on() ? 'ON' : 'OFF' }}</button>`,
})
export class ToggleSwitch {
on = model(false);
flip() { this.on.update(v => !v); }
}
@Component({
selector: 'app-settings',
imports: [ToggleSwitch],
template: `<app-toggle [(on)]="notifications" /> <p>Notifications: {{ notifications() ? 'ON' : 'OFF' }}</p>`,
})
export class Settings {
notifications = signal(true);
}
Mini quiz#
[(size)]="s" is shorthand for…
Want more? Practise all Angular MCQs · Angular interview questions
My notes#
Saved in this browser.