Binding: Radios

Data-binding radio inputs with Aurelia.

Introduction

A group of radio inputs is a type of "single select" interface. Aurelia supports two-way binding any type of property to a group of radio inputs. The examples below illustrate binding number, object, string and boolean properties to sets of radio inputs. In each of the examples there's a common set of steps:

  1. Group the radios via the name property. Radio buttons that have the same value for the name attribute are in the same "radio button group"; only one radio button in a group can be selected at a time.
  2. Define each radio's value using the model property.
  3. Two-way bind each radio's checked attribute to a "selected item" property on the view-model.

Numbers

Let's start with an example that uses a numeric "selected item" property. In this example each radio input will be assigned a number value via the model property. Selecting a radio will cause it's model value to be assigned to the selectedProductId property.


    export class App {
      constructor() {
        this.products = [
          { id: 0, name: 'Motherboard' },
          { id: 1, name: 'CPU' },
          { id: 2, name: 'Memory' },
        ];

        this.selectedProductId = null;
      }
    }
  

    export class App {
      products = [
        { id: 0, name: 'Motherboard' },
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' },
      ];

      selectedProductId = null;
    }
  

    export interface IProduct {
       id: number;
       name: string;
    }

    export class App {
      products: IProduct[] = [
        { id: 0, name: 'Motherboard' },
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' },
      ];

      selectedProductId: number = null;
    }
  

    <template>
      <form>
        <h4>Products</h4>
        <label repeat.for="product of products">
          <input type="radio" name="group1"
                 model.bind="product.id" checked.bind="selectedProductId">
          ${product.id} - ${product.name}
        </label>
        <br />
        Selected product ID: ${selectedProductId}
      </form>
    </template>
  

Objects

The binding system supports binding all types to radios, including objects. Here's an example that binds a group of radios to a selectedProduct object property.


    export class App {
      constructor() {
        products = [
          { id: 0, name: 'Motherboard' },
          { id: 1, name: 'CPU' },
          { id: 2, name: 'Memory' },
        ];

        selectedProduct = null;
      }
    }
  

    export class App {
      products = [
        { id: 0, name: 'Motherboard' },
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' },
      ];

      selectedProduct = null;
    }
  

    export interface IProduct {
       id: number;
       name: string;
    }

    export class App {
      products: IProduct[] = [
        { id: 0, name: 'Motherboard' },
        { id: 1, name: 'CPU' },
        { id: 2, name: 'Memory' },
      ];

      selectedProduct: IProduct = null;
    }
  

    <template>
      <form>
        <h4>Products</h4>
        <label repeat.for="product of products">
          <input type="radio" name="group2"
                 model.bind="product" checked.bind="selectedProduct">
          ${product.id} - ${product.name}
        </label>

        Selected product: ${selectedProduct.id} - ${selectedProduct.name}
      </form>
    </template>
  

Objects with Matcher

You may run into situations where the object your input element's model is bound to does not have reference equality to any of the object in your checked attribute is bound to. The objects might match by id, but they may not be the same object instance. To support this scenario you can override Aurelia's default "matcher" which is a equality comparison function that looks like this: (a, b) => a === b. You can substitute a function of your choosing that has the right logic to compare your objects.


    export class App {
      constructor() {
        this.selectedProduct = { id: 1, name: 'CPU' };

        this.productMatcher = (a, b) => a.id === b.id;
      }
    }
  

    export class App {
      selectedProduct = { id: 1, name: 'CPU' };

      productMatcher = (a, b) => a.id === b.id;
    }
  

    export interface IProduct {
       id: number;
       name: string;
    }

    export class App {
      selectedProduct: IProduct = { id: 1, name: 'CPU' };

      productMatcher = (a, b) => a.id === b.id;
    }
  

    <template>
      <form>
        <h4>Products</h4>
        <label>
          <input type="radio" name="group3"
                 model.bind="{ id: 0, name: 'Motherboard' }"
                 matcher.bind="productMatcher"
                 checked.bind="selectedProduct">
          Motherboard
        </label>
        <label>
          <input type="radio" name="group3"
                 model.bind="{ id: 1, name: 'CPU' }"
                 matcher.bind="productMatcher"
                 checked.bind="selectedProduct">
          CPU
        </label>
        <label>
          <input type="radio" name="group3"
                 model.bind="{ id: 2, name: 'Memory' }"
                 matcher.bind="productMatcher"
                 checked.bind="selectedProduct">
          Memory
        </label>

        Selected product: ${selectedProduct.id} - ${selectedProduct.name}
      </form>
    </template>
  

Booleans

In this example each radio input is assigned one of three literal values: null, true and false. Selecting one of the radios will assign it's value to the likesCake property.


    export class App {
      constructor() {
        this.likesCake = null;
      }
    }
  

    export class App {
      likesCake = null;
    }
  

    export class App {
      likesCake = null;
    }
  

    <template>
      <form>
        <h4>Do you like cake?</h4>
        <label>
          <input type="radio" name="group3"
                 model.bind="null" checked.bind="likesCake">
          Don't Know
        </label>
        <label>
          <input type="radio" name="group3"
                 model.bind="true" checked.bind="likesCake">
          Yes
        </label>
        <label>
          <input type="radio" name="group3"
                 model.bind="false" checked.bind="likesCake">
          No
        </label>

        likesCake = ${likesCake}
      </form>
    </template>
  

Strings

Finally, here's an example using strings. This is example is unique because it does not use model.bind to assign each radio's value. Instead the input's standard value attribute is used. Normally we cannot use the standard value attribute in conjunction with checked binding because it coerces anything it's assigned to a string.


    export class App {
      constructor() {
        this.products = ['Motherboard', 'CPU', 'Memory'];
        this.selectedProduct = null;
      }
    }
  

    export class App {
      products = ['Motherboard', 'CPU', 'Memory'];
      selectedProduct = null;
    }
  

    export class App {
      products: string[] = ['Motherboard', 'CPU', 'Memory'];
      selectedProduct = null;
    }
  

    <template>
      <form>
        <h4>Products</h4>
        <label repeat.for="product of products">
          <input type="radio" name="group4"
                 value.bind="product" checked.bind="selectedProduct">
          ${product}
        </label>
        <br />
        Selected product: ${selectedProduct}
      </form>
    </template>