Reputation: 1037
So, I can change a model value from a child controller, but when the child controller is in ng-switch
then it doesn't work, why? I created an example to demonstrate it.
One way to avoid this is to use the .
in the model name, like bunnies.kills
. Is this a bug or this is a feature ?
Using Angular 1.0.6
Upvotes: 5
Views: 6510
Reputation: 5729
I would take a slightly different approach to this problem.
Rather than use $scope.$parent
, I would recommend you move all of your bunny killing logic into a shared service/model.
Also, I would try to avoid referencing parent views/controllers. Referencing the parent can make it difficult to reuse your code and can be painful to debug as the project grows. It is okay for a parent to know about it's children but a child should know little to nothing about it's parent.
Here is an updated Plunk: http://plnkr.co/edit/PLDbfU8Fu7m59A42qdR6?p=preview
HTML
<body ng-controller="MainCtrl">
<p>
Dead bunnies: <strong>{{Elmer.deadWabbits}}</strong>
</p>
<div ng-controller="SimpleParentCtrl">
<button ng-click="Elmer.killTheWabbit()">Kill from simple parent gun</button>
</div>
<hr>
<div ng-switch="" on="step">
<div ng-switch-when="first" ng-controller="Step1Ctrl">
<button ng-click="Elmer.killTheWabbit()">Kill from 1 tab gun</button>
</div>
<div ng-switch-when="second">
<div ng-controller="Step2Ctrl">
<button ng-click="Elmer.killTheWabbit()">Kill from 2 tab gun</button>
</div>
</div>
</div>
<hr>
<p>
<button ng-click="changeStep('first')">1</button> <button ng-click="changeStep('second')">2</button>
</p>
</body>
JS
angular.module('plunker', []).
service("Elmer", [function() {
this.deadWabbits = 0;
this.killTheWabbit = function() {
this.deadWabbits++;
};
}]).
controller('MainCtrl', function($scope, Elmer) {
$scope.Elmer = Elmer;
$scope.step = 'first';
$scope.changeStep = function(name){
$scope.step = name;
};
}).
controller('SimpleParentCtrl', function() {}).
controller('Step1Ctrl', function() {}).
controller('Step2Ctrl', function() {});
Upvotes: 2
Reputation: 63099
Using your code structure, in your child controllers you would need to change:
$scope.$parent.kills++;
to
$scope.$parent.$parent.kills++;
Explanation: MainCtrl
's scope is the parent scope of SimpleParentCtrl
, but the grandparent of Step1Ctrl
and Step2Ctrl
. As some others pointed out, ng-switch
creates its own scope, and then your Step1Ctrl
and Step2Ctrl
each created a child scope of the ng-switch
.
Note: Each time the 1 or 2 button is clicked, both the ng-switch
and it's currently matched child controller get a new scope.
Also: In case you happen to be looking in the Angular source and wondering how the ng-switch
directive creates its own scope without a scope
property, the answer is that it does so manually in its link method via scope.$new()
. The directives ng-include
, ng-switch
, ng-repeat
, and ng-view
all create new scope this way, either in the link method or the compile method's returned link function.
Resources:
https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance http://www.youtube.com/watch?v=ZhfUv0spHCY&feature=youtu.be&t=30m
Upvotes: 6
Reputation: 19040
One way to avoid this is to use the . in model name, like bunnies.kills. Is this a bug or this is a feature ?
This has been explained numberous times : https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance and in mhevery's video
Upvotes: 1
Reputation: 364707
ng-switch creates its own child scope, which is why @sh0ber's answer is one way to get it to work. In general, models should be referenced in controller scopes (hence reference objects), and not be not primitives. So using a .
is a "best practice".
This is not a bug, but it is not a feature either. This is the way JavaScript prototypal inheritance works with primitives.
Upvotes: 3