Two-Way Binding

Banana-in-a-Box for One-Line Form State

Two-Way Binding

The banana-in-a-box syntax combines a property binding and an event binding so a value flows both ways at once.

4 min read Level 2/5 #angular#templates#forms
What you'll learn
  • Use ngModel from FormsModule on inputs
  • Use two-way binding on a component with a model() input
  • Know when one-way binding is the better choice

Two-way binding wires a value into an element and pulls changes back out with one syntax. It’s so common Angular built a memorable name for the brackets: banana-in-a-box.

ngModel on a Form Input

The classic case: keep a signal in sync with an <input>:

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

@Component({
  selector: 'app-name-form',
  standalone: true,
  imports: [FormsModule],
  template: `
    <input [(ngModel)]="name" />
    <p>Hello, {{ name() }}</p>
  `,
})
export class NameFormComponent {
  name = signal('Ada');
}

You need to import FormsModule into the component’s imports to make ngModel available.

On Custom Components — model() Inputs

Angular 20 added model() signal inputs that pair perfectly with the two-way syntax:

import { Component, model } from '@angular/core';

@Component({
  selector: 'app-rating',
  standalone: true,
  template: `
    <button (click)="value.set(value() - 1)">-</button>
    <span>{{ value() }}</span>
    <button (click)="value.set(value() + 1)">+</button>
  `,
})
export class RatingComponent {
  value = model(0);
}

A parent can then write:

<app-rating [(value)]="stars" />

When the child calls value.set(...), the parent’s stars signal updates too.

When Not to Use It

Two-way binding hides the data flow. For anything more complex than a form input, prefer explicit one-way bindings:

<app-rating [value]="stars()" (valueChange)="onRated($event)" />

You see exactly when data leaves and when it returns — usually worth the extra characters.

Class & Style Binding →