Two-way binding with model signals

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.


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

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

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

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

  5. Test the two-way binding

    Interact with your app to see two-way binding in action:

    1. Click checkboxes - Component updates its own state and notifies parent
    2. Click "Toggle Terms from Parent" - Parent updates propagate down to component
    3. 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() and model.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!