Angular · Components

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.

Intermediate 5 min Lesson 11 of 38Examples target Angular 20 with standalone components. Each lesson states the version it was verified against.
0/38 in Angular
On this page · 5 min · Intermediate

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#

HTML Illustrative
<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#

TypeScriptcart-line.ts Illustrative
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);
}
Expected output
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#

  1. model(1) declares a value the parent can bind with [( )].
  2. The stepper changes its own value with update; Angular pushes the change to the parent’s quantity signal.
  3. [(value)]="quantity" in the parent connects the stepper to the quantity signal.
  4. [(ngModel)]="message" binds the input to a signal; FormsModule must be imported.
  5. 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#

“Can’t bind to ngModel”

FormsModule is missing from the component’s imports.

Binding [( )] to a read-only value

Two-way binding needs something writable — a property or a writable signal, not a computed().

Two-way binding everything

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?
In the child: flip() { this.on.update(v => !v); }. In the parent: <app-toggle [(on)]="notifications" />.
Show solution
TypeScript Illustrative
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#

Two-Way Binding quizMultiple choice Question 1 of 3

[(size)]="s" is shorthand for…

Score so far: 0/0

Want more? Practise all Angular MCQs · Angular interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.