Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ng-change event only fires once for each radio button when created with ng-repeat

I have created an AngularJS directive with radio buttons to control which environment my page will query against and added it to my page. I am using a two-way binding to map a local scope variable called environment to an app variable with the same name. It seems to work well when I create the radio buttons explicitly in the template (in my actual code I'm using templateUrl instead, but still have the same problem).

<div>
    <label><input type="radio" name="env" ng-model="environment" ng-change="changeEnvironment(1)" value="1" />Testing</label>
    <label><input type="radio" name="env" ng-model="environment" ng-change="changeEnvironment(2)" value="2" />Production</label>
</div>

I can select each choice as many times as I want, and the value bubbles all the way up to the app's scope variable.

I wanted to change the creation of the radio buttons to use ng-repeat on an array of choice objects. It creates the options, and it captures the ngChange event, but only once for each choice.

<label ng-repeat="choice in choices">
    <input type="radio" name="env" ng-model="environment" ng-change="changeEnvironment(choice)" value="{{ choice.id }}" />{{ choice.name }}
</label>

Here is the working version fiddle and the relevant parts of my directive code:

template: '<div>'
            + '<label><input type="radio" name="env" ng-model="environment" ng-change="changeEnvironment(1)" value="1" />Testing</label>'
            + '<label><input type="radio" name="env" ng-model="environment" ng-change="changeEnvironment(2)" value="2" />Production</label>'
            + '<div>Directive environment: {{ environment }}</div>'
            + '</div>',
link: function(scope, element, attributes) {

    scope.changeEnvironment = function(choiceID) {
        console.log('SELECTED environment ' + choiceID);
        scope.environment = choiceID;
        console.log('directive environment = ' + scope.environment);
    };

}

And here is the version that only works once:

template: '<div><label ng-repeat="choice in choices">'
            + '<input type="radio" name="env" ng-model="environment" ng-change="changeEnvironment(choice)" value="{{ choice.id }}" />{{ choice.name }}'
            + '</label>'
            + '<div>Directive environment: {{ environment }}</div>'
            + '</div>',
link: function(scope, element, attributes) {
    scope.choices = [
        { id: 1, name: "Testing" },
        { id: 2, name: "Production" }
    ];

    scope.changeEnvironment = function(choice) {
        console.log('SELECTED environment ' + choice.id);
        scope.environment = choice.id;
        console.log('directive environment = ' + scope.environment);
    };

}

I'm brand-new to AngularJS, so it's entirely possible I'm making a very basic mistake. Can anyone point me in the right direction?

UPDATE As per callmekatootie's suggestion, I changed the event in question from ng-change to ng-click, and it fires every time. That will do as a work-around for now, but I originally used ng-change because I didn't think ng-click would apply to changes caused by clicking on the text label rather than the input itself, but in fact it does. Still don't get why ng-change only fires once, though.

like image 541
Danny Avatar asked May 21 '14 17:05

Danny


1 Answers

The cause of the problem is that ngRepeat will create new scopes for its children (along with how prototypal inheritance affects scopes).

The Angular wiki has a fairly good (and thorough) explanation of how scope inheritance works (and the common pitfalls).

This answer to a very similar (in nature) problem pretty much explains the issue.

In your specific case, you could introduse an object (e.g. local) and assign environment as its property):

scope.local = {environment: scope.environment};
scope.changeEnvironment = function(choice) {
    scope.environment = choice.id;
};

Then you should bind your template to that "encaplulated" property:

<input ... ng-model="local.environement" ... />

See, also, this short demo.


UPDATE

This answer intends to point out the root of the issue.
A different implementation (like the ones proposed by tasseKATT or Leon) is definitely recommended (and way more "Angularish").

like image 158
gkalpak Avatar answered Nov 16 '22 02:11

gkalpak