adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/README.md
Now that you've learned passing data to components with input signals, let's explore Angular's model() API for two-way binding. Model signals are perfect for UI components like checkboxes, sliders, or custom form controls where the component needs to both receive a value AND update it.
In this activity, you'll create a custom checkbox component that manages its own state while keeping the parent synchronized.
<hr /> <docs-workflow> <docs-step title="Set up the custom checkbox with model signal"> Create a model signal in the `custom-checkbox` component that can both receive and update the parent's value.// Add imports for model signals
import {Component, model, input, ChangeDetectionStrategy} from '@angular/core';
// Model signal for two-way binding
checked = model.required<boolean>();
// Optional input for label
label = input<string>('');
Unlike input() signals which are read-only, model() signals can be both read and written to.
</docs-step>
<label class="custom-checkbox">
<input type="checkbox" [checked]="checked()" (change)="toggle()" />
<span class="checkmark"></span>
{{ label() }}
</label>
The component reads from its model signal and has a method to update it. </docs-step>
<docs-step title="Add the toggle method"> Implement the toggle method that updates the model signal when the checkbox is clicked.toggle() {
// This updates BOTH the component's state AND the parent's model!
this.checked.set(!this.checked());
}
When the child component calls this.checked.set(), it automatically propagates the change back to the parent. This is the key difference from input() signals.
</docs-step>
// Parent signal models
agreedToTerms = model(false);
enableNotifications = model(true);
// Methods to test two-way binding
toggleTermsFromParent() {
this.agreedToTerms.set(!this.agreedToTerms());
}
resetAll() {
this.agreedToTerms.set(false);
this.enableNotifications.set(false);
}
Then update the template:
Part 1. Uncomment the checkboxes and add two-way binding:
___ADD_TWO_WAY_BINDING___ with [(checked)]="agreedToTerms" for the first checkbox___ADD_TWO_WAY_BINDING___ with [(checked)]="enableNotifications" for the secondPart 2. Replace the ??? placeholders with @if blocks:
@if (agreedToTerms()) {
Yes
} @else {
No
}
@if (enableNotifications()) {
Yes
} @else {
No
}
Part 3. Add click handlers to the buttons:
<button (click)="toggleTermsFromParent()">Toggle Terms from Parent</button>
<button (click)="resetAll()">Reset All</button>
The [(checked)] syntax creates two-way binding - data flows down to the component AND changes flow back up to the parent by emitting an event that references the signal itself and does not call the signal getter directly.
</docs-step>
Both the parent and child can update the shared state, and both stay in sync automatically! </docs-step>
</docs-workflow>Perfect! You've learned how model signals enable two-way binding:
model() and model.required() for values that can be both read and written[(property)] syntax to bind parent signals to child modelsWhen to use model() vs input():
input() for data that only flows down (display data, configuration)model() for UI components that need to update their own value (form controls, toggles)In the next lesson, you'll learn about using signals with services!