[angularjs] AngularJS - Access to child scope

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?

This question is related to angularjs

The answer is


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.

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.


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.


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


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>

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 :)