Model Signals โ model()
model() is a writable signal that supports two-way binding with parent components. It combines @Input, @Output, and signal() into a single declarative API.
model() is a special type of signal introduced in Angular 17.3+ that supports two-way binding. Think of it as signal() + @Input() + @Output() combined into one line.
"model = two-way signal โ template se change karo ya TypeScript se, dono sync."
import { Component, model } from '@angular/core';
@Component({
selector: 'app-biryani-card',
template: `
<h3>{{ name() }}</h3>
<p>โน{{ price() }}</p>
<input [ngModel]="name()" (ngModelChange)="name.set($event)">
`
})
export class BiryaniCardComponent {
// model() = signal + @Input + @Output in one line!
name = model('Biryani');
price = model(0);
}
How model() solves the old two-way problem:
- Old way:
@Input() name + @Output() nameChange + EventEmitter= 5+ lines - New way:
name = model('')= 1 line - Parent still uses:
[(name)]="myName"โ same syntax! - Inside component, model() works exactly like signal() โ read with (), write with .set()/.update()
Using model() in a child component is straightforward:
import { Component, model } from '@angular/core';
@Component({
selector: 'app-biryani-card',
standalone: true,
template: `
<div class="card" [class.selected]="selected()">
<h3>{{ name() }}</h3>
<span class="price">โน{{ price() }}</span>
<span class="rating">โญ {{ rating() }}</span>
<button (click)="toggleSelect()">
{{ selected() ? 'Deselect' : 'Select' }}
</button>
</div>
`
})
export class BiryaniCardComponent {
// model() signals โ support two-way binding
name = model.required<string>();
price = model(0);
rating = model(4.5);
selected = model(false);
}
// Parent component uses:
// <app-biryani-card [(name)]="selectedBiryani">
"model() = signal + @Input + @Output combined into one line โ 3-in-1."
How it works: model() creates a writable signal AND automatically creates a corresponding @Output() nameChange event emitter. When you call this.name.set('new'), it updates the signal AND emits nameChange so the parent knows to update its binding.
Inside the component's own template, model() can be used with either one-way or two-way binding:
<!-- Reading model() value -->
<h3>{{ name() }}</h3>
<!-- One-way binding: input displays model, but changes don't update model -->
<input [value]="name()">
<!-- Two-way with plain input: need to handle change manually -->
<input [value]="name()" (input)="name.set($any($event.target).value)">
<!-- Two-way with Angular's [model] directive (shorthand) -->
<input [model]="name">
<!-- With ngModel โ still works -->
<input [(ngModel)]="name">
Angular has a special [model] directive that works directly with model() signals โ no need for (ngModelChange) or manual event handling.
"[model] directive = model signal ke saath direct two-way โ no ngModel needed."
With controls:
<!-- Checkbox -->
<input type="checkbox" [model]="isAvailable">
<!-- Select -->
<select [model]="category">
<option value="Veg">Veg</option>
<option value="Non-Veg">Non-Veg</option>
</select>Here's the comparison between all approaches for child-parent communication:
| signal() | model() | @Input + @Output | |
|---|---|---|---|
| Lines of code | 1 | 1 | 5-7 |
| Two-way binding | โ No | โ Yes | โ Yes (manual) |
| Writable inside component | โ Yes | โ Yes | โ Input is read-only |
| Signal ecosystem | โ Yes | โ Yes | โ No |
| Parent usage | N/A | [(name)]="x" | [(name)]="x" |
| Auto-change detection | โ Yes | โ Yes | โ ๏ธ Only with OnPush |
// โ WRONG: signal() for two-way โ parent can't bind
name = signal('Biryani');
// <app-biryani [(name)]="x"> โ DOESN'T WORK!
// โ
RIGHT: model() for two-way
name = model('Biryani');
// <app-biryani [(name)]="x"> โ WORKS!
// โ OLD: @Input + @Output (boilerplate)
@Input() name = '';
@Output() nameChange = new EventEmitter<string>();
updateName(val: string) { this.name = val; this.nameChange.emit(val); }
"model() = 3-in-1 โ signal + input + output. signal() = internal state only."
model() supports configuration options for flexibility:
required โ must be provided by parent:
// Parent MUST provide this binding โ TypeScript error if not
name = model.required<string>();
// <app-biryani [(name)]="someValue"> โ REQUIRED
alias โ rename for parent usage:
// Component property is 'name', parent uses 'biryaniName'
name = model('', { alias: 'biryaniName' });
// Parent: <app-biryani [(biryaniName)]="myName">
initializer โ lazy initialization:
name = model('', { initializer: () => getDefaultName() });
// Uses function result as initial value, not called unnecessarily
Combined example:
export class BiryaniCardComponent {
// Required model with alias
biryaniItem = model.required<Biryani>({ alias: 'item' });
// Required simple value
name = model.required<string>();
// Optional with custom initializer
rating = model(4.5, { initializer: () => loadDefaultRating() });
// Simple optional
price = model(0);
}
// Parent usage:
// <app-biryani-card [(item)]="biryaniData" [(name)]="biryaniName">
"Options = customization โ alias do, required banao, default value do. model() handles everything."
Key Takeaways
- โ model() = signal + @Input + @Output combined โ 3-in-1
- โ Two-way binding: parent uses [(name)]="value" syntax
- โ Writable inside component โ .set() and .update() work like signal()
- โ [model] directive โ works with model() for template two-way binding
- โ Options: required, alias, initializer
- โ model() replaces @Input + @Output + EventEmitter boilerplate
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login