The Banana-in-a-Box Syntax
Two-way binding combines property binding [ ] and event binding ( ) into a single syntax: [( )]. It keeps a component field and a form input in sync.
Basic ngModel Usage
@Component({
selector: 'app-name-input',
template: `
<input [(ngModel)]="name" placeholder="Enter name">
<p>Hello, {{ name }}!</p>
`,
})
export class NameInputComponent {
name = '';
}
When the user types in the input, name updates automatically. When name changes in the component, the input updates automatically.
What [(ngModel)] Really Means
<!-- This: -->
<input [(ngModel)]="name">
<!-- Is shorthand for this: -->
<input [ngModel]="name" (ngModelChange)="name = $event">
Requirements
ngModel requires FormsModule to be imported:
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-example',
imports: [FormsModule],
template: `<input [(ngModel)]="value">`,
})
export class ExampleComponent {
value = '';
}
Multiple Fields
@Component({
selector: 'app-user-form',
imports: [FormsModule],
template: `
<input [(ngModel)]="user.firstName" placeholder="First name">
<input [(ngModel)]="user.lastName" placeholder="Last name">
<input [(ngModel)]="user.email" type="email" placeholder="Email">
<input [(ngModel)]="user.age" type="number" placeholder="Age">
<pre>{{ user | json }}</pre>
`,
})
export class UserFormComponent {
user = { firstName: '', lastName: '', email: '', age: 0 };
}
Two-Way Binding with Custom Components
You can implement two-way binding in your own components using model() or @Input + @Output.
Using model() (Modern)
@Component({
selector: 'app-counter',
template: `
<button (click)="decrement()">-</button>
<span>{{ count() }}</span>
<button (click)="increment()">+</button>
`,
})
export class CounterComponent {
count = model<number>(0);
increment(): void {
this.count.update(c => c + 1);
}
decrement(): void {
this.count.update(c => c - 1);
}
}
<app-counter [(count)]="parentCount"></app-counter>
Using @Input + @Output (Traditional)
@Component({
selector: 'app-rating',
template: `
<span *ngFor="let star of [1,2,3,4,5]"
(click)="setRating(star)"
[class.filled="star <= rating">
★
</span>
`,
})
export class RatingComponent {
@Input() rating = 0;
@Output() ratingChange = new EventEmitter<number>();
setRating(value: number): void {
this.rating = value;
this.ratingChange.emit(value);
}
}
<app-rating [(rating)]="productRating"></app-rating>
Template-Driven vs Reactive Forms
| Feature | Template-Driven (ngModel) | Reactive Forms |
|---|---|---|
| Setup | Minimal — just import FormsModule | More boilerplate |
| Validation | Template directives | Component class |
| Dynamic fields | Difficult | Easy (FormArray) |
| Testing | Harder (DOM-dependent) | Easier (class-based) |
| Complex logic | Harder to manage | Clean separation |
| Best for | Simple forms | Complex forms |
For complex forms, prefer reactive forms (covered in Phase 524).
Quiz
1. What is the syntax for two-way binding?
2. What module must be imported to use ngModel?
3. What is the modern signal-based way to create a two-way bindable component?
Flashcards
Question
What is two-way binding syntax?
Click to reveal answer
Answer
[(property)] — banana-in-a-box. Combines [property] binding and (event) binding.
Question
What module does ngModel need?
Click to reveal answer
Answer
FormsModule from @angular/forms.
Question
How do you make a custom two-way bindable component?
Click to reveal answer
Answer
Use model() signal, or @Input + @Output with matching Change suffix (e.g., rating + ratingChange).
Revision Notes
Key Takeaways
- 1. [(ngModel)] syncs component fields with form inputs
- 2. FormsModule must be imported for ngModel
- 3. model() creates two-way bindable signals for custom components
- 4. Prefer reactive forms for complex scenarios
Interview Tips
- • Explain that [(ngModel)] is shorthand for [ngModel] + (ngModelChange)
- • Be ready to compare template-driven vs reactive forms
- • Mention model() as the modern approach for custom components
Cheat Sheet
Two-Way Binding Cheat Sheet
Banana-in-a-box:
<input [(ngModel)]="name">
Shorthand for:
<input [ngModel]="name" (ngModelChange)="name = $event">
Custom component (model):
count = model<number>(0);
<app-counter [(count)]="parentCount"></app-counter>
Custom component (traditional):
@Input() rating = 0;
@Output() ratingChange = new EventEmitter<number>();