angularjs: Change parent scope from controller within a ng-switch
Asked Answered
E

4

5

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

Exaction answered 22/5, 2013 at 20:14 Comment(1)
@sh0ber is correct. The reason is because ng-switch creates its own scope. Which means you really have two nested levels of scope from the parent.Boehm
I
6

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

Insecticide answered 22/5, 2013 at 20:18 Comment(0)
K
3

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.

Kapp answered 22/5, 2013 at 20:21 Comment(2)
Using a .? Can you explain?Hallee
@chovy, when you create an object in the parent scope (or better, reference one in the parent controller -- e.g., $scope.refToSomeOtherObj = MyService.someObj;), child scopes (such a child scope is created by the ng-switch directive) get a reference to that object. So you can directly reference the object properties in the child scope: $scope.refToSomeOtherObj.somePropertyOfSomeObj = ... I.e., you don't have to use $parent, which is a fragile way to code... should your controller hierarchy change, $parent may not work anymore.Kapp
W
2

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() {});
Willi answered 28/6, 2013 at 4:22 Comment(1)
This is much better approach, and you'r right about the "child should know little to nothing about it's parent"Exaction
S
1

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

Shad answered 22/5, 2013 at 20:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.