AngularJSTwo-Way Binding (ngModel)

Two-Way Binding in Angular

Two-way binding synchronises a component property and a template input in both directions at the same time. When the user changes the input the component property updates; when the component property changes the input reflects the new value. Angular's "banana-in-a-box" syntax [(ngModel)] is the most common form, but two-way binding extends to any component that follows the pattern.

The Banana-in-a-Box Syntax

The nickname comes from the way it looks: [( )] — square brackets inside parentheses. Conceptually it combines property binding [ ] (class → template) and event binding ( ) (template → class) into one.

HTML
<!-- Two-way binding -->
[(ngModel)]="username"

<!-- Equivalent long form -->
[ngModel]="username" (ngModelChange)="username = $event"
Note
Both forms are identical in behaviour. The long form is useful when you need to run extra logic in the change handler: \`(ngModelChange)="onNameChange(\$event)"\`.
Setting Up ngModel

ngModel lives in FormsModule. Import it in your standalone component or NgModule before using it.

TS
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-profile-form',
  standalone: true,
  imports: [FormsModule],   // <-- required
  template: `
    <label>
      Name:
      <input type="text" [(ngModel)]="name" />
    </label>
    <label>
      Email:
      <input type="email" [(ngModel)]="email" />
    </label>
    <p>Preview: {{ name }} &lt;{{ email }}&gt;</p>
  `,
})
export class ProfileFormComponent {
  name = 'Alice';
  email = 'alice@example.com';
}
Warning
If you forget to import \`FormsModule\`, Angular will silently ignore \`ngModel\` in production (no error in template) but throw a compile-time warning. Always add \`FormsModule\` to \`imports\`.
Two-Way Binding with Different Input Types

TS
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-input-types',
  standalone: true,
  imports: [FormsModule],
  template: `
    <!-- Text input -->
    <input type="text"     [(ngModel)]="text"     placeholder="Text" />

    <!-- Number input -->
    <input type="number"   [(ngModel)]="quantity"  min="0" max="99" />

    <!-- Checkbox -->
    <input type="checkbox" [(ngModel)]="agreed" id="terms" />
    <label for="terms">I agree to the terms</label>

    <!-- Radio buttons -->
    <input type="radio" [(ngModel)]="size" value="small"  id="sm" /><label for="sm">Small</label>
    <input type="radio" [(ngModel)]="size" value="medium" id="md" /><label for="md">Medium</label>
    <input type="radio" [(ngModel)]="size" value="large"  id="lg" /><label for="lg">Large</label>

    <!-- Select / dropdown -->
    <select [(ngModel)]="country">
      <option value="us">United States</option>
      <option value="ca">Canada</option>
      <option value="uk">United Kingdom</option>
    </select>

    <!-- Textarea -->
    <textarea [(ngModel)]="notes" rows="4"></textarea>

    <pre>{{ currentValues() }}</pre>
  `,
})
export class InputTypesComponent {
  text     = 'Hello';
  quantity = 1;
  agreed   = false;
  size     = 'medium';
  country  = 'ca';
  notes    = '';

  currentValues() {
    return JSON.stringify({
      text: this.text,
      quantity: this.quantity,
      agreed: this.agreed,
      size: this.size,
      country: this.country,
    }, null, 2);
  }
}
Two-Way Binding Without FormsModule (Reactive Alternative)

In reactive forms you bind to the form control, not ngModel. This is the recommended approach for complex forms.

TS
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-reactive-input',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <input [formControl]="nameControl" />
    <p>Value: {{ nameControl.value }}</p>
    <p>Valid: {{ nameControl.valid }}</p>
  `,
})
export class ReactiveInputComponent {
  nameControl = new FormControl('Angular');
}
Tip
For simple forms with a few fields, \`ngModel\` (template-driven) is quick and easy. For complex forms with validation, dynamic fields, or programmatic control, reactive forms (\`FormControl\`, \`FormGroup\`) are preferred.
Two-Way Binding Between Components

Any component can support two-way binding if it follows the value/valueChange convention: expose an @Input() named x and an @Output() named xChange (same name + "Change" suffix). The parent can then use [(x)]="property".

TS
// counter.component.ts (child)
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <button (click)="decrement()">-</button>
    <span>{{ value }}</span>
    <button (click)="increment()">+</button>
  `,
})
export class CounterComponent {
  @Input()  value = 0;
  @Output() valueChange = new EventEmitter<number>();

  increment() { this.valueChange.emit(this.value + 1); }
  decrement() { this.valueChange.emit(this.value - 1); }
}

TS
// parent component
import { Component } from '@angular/core';
import { CounterComponent } from './counter.component';

@Component({
  selector: 'app-shop',
  standalone: true,
  imports: [CounterComponent],
  template: `
    <h2>Shopping Cart</h2>
    <app-counter [(value)]="cartQuantity" />
    <p>You want {{ cartQuantity }} item(s)</p>
  `,
})
export class ShopComponent {
  cartQuantity = 1;
}
Note
The \`xChange\` pattern is what Angular uses internally for \`ngModel\`. \`[(ngModel)]="x"\` desugars to \`[ngModel]="x" (ngModelChange)="x = \$event"\`.
Two-Way Binding with Angular Signals (model())

Angular 17.1+ introduced model() — a signal-based alternative to the @Input / @Output + EventEmitter pattern. It makes two-way binding more ergonomic and pairs naturally with the Signals API.

TS
// toggle.component.ts — using model()
import { Component, model } from '@angular/core';

@Component({
  selector: 'app-toggle',
  standalone: true,
  template: `
    <button
      [class.on]="checked()"
      (click)="checked.set(!checked())"
    >
      {{ checked() ? 'ON' : 'OFF' }}
    </button>
  `,
})
export class ToggleComponent {
  checked = model(false);  // writable signal + @Input + @Output in one
}

TS
// parent using two-way binding with model()
@Component({
  selector: 'app-settings',
  standalone: true,
  imports: [ToggleComponent],
  template: `
    <app-toggle [(checked)]="darkMode" />
    <p>Dark mode: {{ darkMode() }}</p>
  `,
})
export class SettingsComponent {
  darkMode = signal(false);
}
Comparison: ngModel vs Reactive Forms vs model()

Approach

Module Required

Best For

Angular Version

[(ngModel)]

FormsModule

Simple template-driven forms

All

FormControl + formControl

ReactiveFormsModule

Complex forms with validation

All

model() signal

None (standalone)

Component-to-component two-way binding

17.1+

Common Pitfalls
  • Forgetting FormsModule — ngModel silently fails without it.

  • Mutating objects directly — use spread (...) or Object.assign() to trigger change detection.

  • Binding to computed values — [(ngModel)]="user.name + suffix" is read-only, not writeable.

  • Mixing ngModel and reactive forms — use one or the other, not both on the same input.

  • Using two-way binding for read-only display — prefer one-way [property] binding for display-only values.

Summary
  • [(ngModel)] is the classic two-way binding — combine [ngModel] + (ngModelChange) under the hood.

  • Import FormsModule in the component or NgModule before using ngModel.

  • Works on text, number, checkbox, radio, select, and textarea inputs.

  • Component-level two-way binding: pair @Input() x with @Output() xChange.

  • Angular 17.1+ model() signal is the modern signal-based two-way binding primitive.

  • For complex forms, prefer ReactiveFormsModule (FormControl / FormGroup).