OdeToCode IC Logo

Radio Buttons with AngularJS

Tuesday, June 25, 2013

Last week we looked at using ngOptions in AngularJS to build a select list. Since the selections are mutually exclusive, we could have done something similar using radio buttons. There is no directive dedicated to radio buttons, but here’s a couple examples to show how easy they are to work with.

We’ll use the same controller as last time, but add a project attribute to the engineer.

var EngineeringController = function($scope) {

    $scope.engineer = {
        name: "Dani",
        currentActivity: "Testing code",
        project: "App A"
    };

    $scope.activities =        
        [
            "Writing code", "Testing code",
            "Fixing bugs", "Dancing"
        ];              
};

If we want to assign a project using hard coded inputs in the view, those inputs are easy to create and bind using ng-model.

<div data-ng-controller="EngineeringController">
    {{engineer.name }} is currently 
    {{ engineer.currentActivity }} for
    {{ engineer.project }}
    <label>
        <input type="radio" name="project" 
               value="App A" ng-model="engineer.project" />
        App A
    </label>
    <label>
        <input type="radio" name="project" 
               value="App B" ng-model="engineer.project" />
        App B
    </label>
    ...
</div>

A more interesting challenge is dynamically building a series of radio buttons using the activities array in the model. Here’s one approach using ng-repeat:

Choose a new activity:
<label data-ng-repeat="act in activities">
    <input
        name="currentActivity"
        type="radio"
        value="{{act}}"
        ng-model="engineer.currentActivity" />
        {{act}}
</label>

radio buttons in angularjsThe above works because all the inputs have the same name, bind to the same model property, and have distinct activity values. But, there’s one other feature of the model that makes the sample work, and that’s how we are binding to a nested property on the model (engineer.currentActivity).

Remember that an ng-repeat directive creates a child scope for each item, and the child scope inherits from the controller’s $scope object. Thus, the two way binding established with ng-model might not be setting a value in the $scope object of the controller, but in a child scope created by the repeater. In the code above, the binding works because the binding first needs to read the engineer property which is prototypically inherited from the $scope of the model, and then writes to the currentActivity property of that engineer object.

If the controller was flat like the following:

var EngineeringController = function ($scope) {

    $scope.currentActivity = "Testing code";

    $scope.activities =
        [
            "Writing code", "Testing code",
            "Fixing bugs", "Dancing"
        ];
};

Then the following markup would never write into $scope.currentActivity, but instead into a child scope, so this approach is probably not what you want.

<label data-ng-repeat="act in activities">
    <input
        name="currentActivity"
        type="radio"
        value="{{act}}"
        ng-model="currentActivity" />
        {{act}}
</label>

One way to fix the problem is to go back to using a more complex model (engineer.currentActivity would work), or to explicitly bind to the parent scope of the repeat item using $parent.

<input
    name="currentActivity"
    type="radio"
    value="{{act}}"
    ng-model="$parent.currentActivity" />