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.
-
Set up the custom checkbox with model signal
Create a model signal in the
custom-checkboxcomponent that can both receive and update the parent's value.// Add imports for model signalsimport {Component, model, input, ChangeDetectionStrategy} from '@angular/core';// Model signal for two-way bindingchecked = model.required<boolean>();// Optional input for labellabel = input<string>('');Unlike
input()signals which are read-only,model()signals can be both read and written to. -
Create the checkbox template
Build the checkbox template that responds to clicks and updates its own model.
<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.
-
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 frominput()signals. -
Set up two-way binding in the parent
First, uncomment the model signal properties and methods in
app.ts:// Parent signal modelsagreedToTerms = model(false);enableNotifications = model(true);// Methods to test two-way bindingtoggleTermsFromParent() { 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:
- Replace
___ADD_TWO_WAY_BINDING___with[(checked)]="agreedToTerms"for the first checkbox - Replace
___ADD_TWO_WAY_BINDING___with[(checked)]="enableNotifications"for the second
Part 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. - Replace
-
Test the two-way binding
Interact with your app to see two-way binding in action:
- Click checkboxes - Component updates its own state and notifies parent
- Click "Toggle Terms from Parent" - Parent updates propagate down to component
- Click "Reset All" - Parent resets both models and components update automatically
Both the parent and child can update the shared state, and both stay in sync automatically!
Perfect! You've learned how model signals enable two-way binding:
- Model signals - Use
model()andmodel.required()for values that can be both read and written - Two-way binding - Use
[(property)]syntax to bind parent signals to child models - Perfect for UI components - Checkboxes, form controls, and widgets that need to manage their own state
- Automatic synchronization - Parent and child stay in sync without manual event handling
When to use model() vs input():
- Use
input()for data that only flows down (display data, configuration) - Use
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!