113

If I have the following controllers:

function parent($scope, service) {
    $scope.a = 'foo';

    $scope.save = function() {
        service.save({
            a:  $scope.a,
            b:  $scope.b
        });
    }
}

function child($scope) {
    $scope.b = 'bar';
}

What's the proper way to let parent read b out of child? If it's necessary to define b in parent, wouldn't that make it semantically incorrect assuming that b is a property that describes something related to child and not parent?

Update: Thinking further about it, if more than one child had b it would create a conflict for parent on which b to retrieve. My question remains, what's the proper way to access b from parent?

Aziz Alfoudari
  • 5,193
  • 7
  • 37
  • 53
  • 1
    Also, be sure you read this: http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs A very good and in-depth overview of scopes and inheritance in angularJS. – Martijn Jul 15 '13 at 07:06

6 Answers6

163

Scopes in AngularJS use prototypal inheritance, when looking up a property in a child scope the interpreter will look up the prototype chain starting from the child and continue to the parents until it finds the property, not the other way around.

Check Vojta's comments on the issue https://groups.google.com/d/msg/angular/LDNz_TQQiNE/ygYrSvdI0A0J

In a nutshell: You cannot access child scopes from a parent scope.

Your solutions:

  1. Define properties in parents and access them from children (read the link above)
  2. Use a service to share state
  3. Pass data through events. $emit sends events upwards to parents until the root scope and $broadcast dispatches events downwards. This might help you to keep things semantically correct.
jaime
  • 41,961
  • 10
  • 82
  • 52
  • 8
    Plus the different between $emit and $broadcast is where $emit is cancel-able and $broadcast dont. – Azri Jamil Nov 18 '12 at 15:05
  • One place you probably want to get the child scope is when unit testing directives. If you have a transcluded directive the scope is a child of the scope used to compile the element. Getting access to the scope of the compiled directive for testing is challenging. – pmc Jul 29 '14 at 02:13
  • Thanks. Another option could be `localStorage` with a dependency like `ngStorage`. – Aakash Jul 20 '16 at 03:12
84

While jm-'s answer is the best way to handle this case, for future reference it is possible to access child scopes using a scope's $$childHead, $$childTail, $$nextSibling and $$prevSibling members. These aren't documented so they might change without notice, but they're there if you really need to traverse scopes.

// get $$childHead first and then iterate that scope's $$nextSiblings
for(var cs = scope.$$childHead; cs; cs = cs.$$nextSibling) {
    // cs is child scope
}

Fiddle

Jussi Kosunen
  • 8,277
  • 3
  • 26
  • 34
49

You can try this:

$scope.child = {} //declare it in parent controller (scope)

then in child controller (scope) add:

var parentScope = $scope.$parent;
parentScope.child = $scope;

Now the parent has access to the child's scope.

George Netu
  • 2,758
  • 4
  • 28
  • 49
Farzin Mo
  • 499
  • 4
  • 2
  • 1
    Very clean and straightforward. To add to this just realize that there is no binding so when you declare it like above the parentScope.child is holding the childs scope at that moment. To solve this you can add $scope.$watch(function(){parentScope.child =$scope}); so that after every digest it pushes the new scope up to the parent. On the parent end if you are using any of the child scopes for visuals in the html you will need add a watch on its child variable and tell it to update the scope like so: $scope.$watch('child',function() {$scope.$evalAsync();}); . Hope this saves someone some time! – IfTrue Sep 03 '15 at 18:22
  • 2
    @IfTrue I believe a $watch() is not required. These are objects and they pass by reference. – Swanidhi Sep 30 '15 at 19:51
  • 2
    @Swanidhi = I know what you mean and I thought so as well but it didn't work when I was experimenting at the time of writing the comment. For what it is worth I switched from doing this to emit/broadcasting. – IfTrue Sep 30 '15 at 20:07
  • 1
    How to do this in Type Script ? – ATHER Jan 23 '16 at 03:38
  • Best Answer for me!! Best example of Objects treated as by reference in javascript – Vikas Gautam Jul 01 '16 at 01:07
  • Holy crap this is so clever, if a little unconventional. Seriously helped me out of a jam! – Alex McCabe Aug 23 '16 at 13:54
  • this is true source of bugs. you link tightly parent and child scope what is not good. first refactoring and making another intermediate scope between parent and child will ruin this architecture. The rule of thumb is that child should never access parent - never! If child needs to communicate with parent, then there are bubbling events. See https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/ – walv Jan 27 '17 at 01:42
4

One possible workaround is inject the child controller in the parent controller using a init function.

Possible implementation:

<div ng-controller="ParentController as parentCtrl">
   ...

    <div ng-controller="ChildController as childCtrl" 
         ng-init="ChildCtrl.init()">
       ...
    </div>
</div>

Where in ChildController you have :

app.controller('ChildController',
    ['$scope', '$rootScope', function ($scope, $rootScope) {
    this.init = function() {
         $scope.parentCtrl.childCtrl = $scope.childCtrl;
         $scope.childCtrl.test = 'aaaa';
    };

}])

So now in the ParentController you can use :

app.controller('ParentController',
    ['$scope', '$rootScope', 'service', function ($scope, $rootScope, service) {

    this.save = function() {
        service.save({
            a:  $scope.parentCtrl.ChildCtrl.test
        });
     };

}])

Important:
To work properly you have to use the directive ng-controller and rename each controller using as like i did in the html eg.

Tips:
Use the chrome plugin ng-inspector during the process. It's going to help you to understand the tree.

borracciaBlu
  • 4,017
  • 3
  • 33
  • 41
3

Using $emit and $broadcast, (as mentioned by walv in the comments above)

To fire an event upwards (from child to parent)

$scope.$emit('myTestEvent', 'Data to send');

To fire an event downwards (from parent to child)

$scope.$broadcast('myTestEvent', {
  someProp: 'Sending you some data'
});

and finally to listen

$scope.$on('myTestEvent', function (event, data) {
  console.log(data);
});

For more details :- https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/

Enjoy :)

Anjana Silva
  • 8,353
  • 4
  • 51
  • 54
2

Yes, we can assign variables from child controller to the variables in parent controller. This is one possible way:

Overview: The main aim of the code, below, is to assign child controller's $scope.variable to parent controller's $scope.assign

Explanation: There are two controllers. In the html, notice that the parent controller encloses the child controller. That means the parent controller will be executed before child controller. So, first setValue() will be defined and then the control will go to the child controller. $scope.variable will be assigned as "child". Then this child scope will be passed as an argument to the function of parent controller, where $scope.assign will get the value as "child"

<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script type="text/javascript">
    var app = angular.module('myApp',[]);

    app.controller('child',function($scope){
        $scope.variable = "child";
        $scope.$parent.setValue($scope);
    });

    app.controller('parent',function($scope){
        $scope.setValue = function(childscope) {
            $scope.assign = childscope.variable;
        }
    });

</script>
<body ng-app="myApp">
 <div ng-controller="parent">
    <p>this is parent: {{assign}}</p>
    <div ng-controller="child">
        <p>this is {{variable}}</p>
    </div>
 </div>
</body>
</html>
rupali317
  • 472
  • 1
  • 9
  • 22