All we need is an easy explanation of the problem, so here it is.
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.
How to solve :
I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.
Method 1
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”).
Method 2
Use an object instead of a primitive value:
app.controller('mainController', ['$scope', function (scope) {
scope.environment = { value: '' };
}]);
Remove ng-change
and $watch
. ng-model
will be enough (unless I’m misunderstanding the use case).
Demo: http://jsfiddle.net/GLAQ8/
Very good post on prototypal inheritance can be found here.
Method 3
you are using enviorement instead of $parent.enviorement in your ng-change event which is tied to the repeat scope not the the ng-repeat parent scope where the enviorement variable lives,
ng-model="$parent.enviroment"
notice that in this case you don’t even need the events to keep enviorement updated and any changes in the model will refelct in the radio buttons
Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0