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.
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 →