AngularJS - Access to child scope - angularjs

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?

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:
Define properties in parents and access them from children (read the link above)
Use a service to share state
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.

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

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.

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.

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

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>

Related

How to assign a change in value of a variable from child controller to parent controller in AngularJS?

In this code, we have a parent controller and a child controller. In the HTML, notice that the parent controller encloses the child controller. In the parent controller, I am using a $controller service, which gets the child controller's scope. child controller's $scope.Child will be assigned to $scope.Parent. There is a button when clicked, would change the $scope.Child to "new child" value in child controller.
I am trying to achieve the same change in the parent controller as well. I was expecting the $scope.Parent to get the value as "new child" as well by putting a $watch() on $scope.Child. But the change in the value of child controller's $scope.Child is not being reflected in $scope.Parent
So, my question is how do I assign the change in the value of $scope.Child in the child controller to the parent controller?
<!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('parent', function($scope,$controller){
var childScope = $controller('child',{$scope: $scope});
$scope.Parent = $scope.Child;
$scope.$watch('Child',function(){
$scope.Parent = $scope.Child;
},true);
});
app.controller('child',function($scope){
$scope.Child = "child";
$scope.changeValue = function() {
$scope.Child = "new child";
}
});
</script>
<body ng-app="myApp">
<div ng-controller="parent">
<p>this is parent: {{Parent}}</p>
<div ng-controller="child">
<p>this is {{Child}}</p>
<button ng-click="changeValue()">Click!</button>
</div>
</div>
</body>
</html>
Use $emit - dispatches the event upwards through the scope hierarchy, a.e. in basic words - notify parent controller from child controller.
$scope.changeValue = function() {
$scope.Child = "new child";
$scope.$emit('onChildChange', {data: $scope.Child});
}
In parent controller:
$scope.$on('onChildChange', function (event, result) {
console.log(result.data)
});
Some Fiddle demo how it works
about your example:
var childScope = $controller('child',{$scope: $scope});
$scope.Parent = $scope.Child;
$scope.$watch('Child',function(){
$scope.Parent = $scope.Child;
},true);
Its not good practice to load child scope in parent controller. It can cause to unexpected behavior and its really hard to handle this kind of code.
Further, deep watch a.e. $scope.$watch({}, true) is a overkill that will effect on performance. In our case $emit will fire event and who listens on this event will catch it. No redundant watchers here

Angularjs: How to build a controller reuseable?

I have an application with some controllers that have the same functions as: InitMenu, GetData, Search, Paging, ..
How can I build a generic controller with the main functions: InitMenu, GetData, Search, Paging, .. without having to write in each specific controller?
share all the common logic inside a service and inject it inside your controllers.
.service('CommonData, function(){
this.getData = function(){
}
this.InitMenu= function(){
}
this.search= function(){
}
})
otherwise you can make a parent controller and all the child controllers will prototype inherit from the parent scope.
.controller('ParentCtrl', function($scope){
$scope.myObj = {};
$scope.parentMethod = function(){
return myObj;
}
})
.controller('ChildCtrl', function($scope){
var stuff = $scope.parentMethod();
//you can access the parent method in the child template as well
})
<div ng-controller="ParentCtrl">
<div ng-controller="ChildCtrl">
{{parentMethod()}}
//if you use controllAs syntax change it to {{parentCtrlName.parentMethod()}}
</div>
</div>
if you use controllerAs syntax you can access the parent $scope by specifyng the controller name as a scope field in the child controller. e.g $scope.parentCtrlName.parentMethod() and parentCtrlname.parentMethod() in the child template.

Programmatically creating new instances of a controller

So here is my problem, I have some functions/variables in a parent controller
function parentController($scope) {
$scope.numberOfChildren = $scope.numberOfChildren + 1 || 1;
console.log($scope.numberOfChildren);
$scope.someFunction = function(argument) {
// do stuff
$scope.someVariable = result of the function
}
}
I am calling this controller in two other controllers that are directives controllers and are called in the same view
function firstChildController ($scope, $controller) {
var aVariable = 1;
$scope.otherVariable = 10;
$controller('parentController', {$scope: $scope});
$scope.someFunction(aVariable);
}
function secondChildController ($scope, $controller) {
var aVariable = 6;
$scope.otherVariable = 11;
$controller('parentController', {$scope: $scope});
$scope.someFunction(aVariable);
}
What I want, is not to share the parent scope for the two children.
Right now, there is only one instance of the parent controller and so when I call two directives depending on it on the same view, I get $scope.numberOfChildren === 2.
What I want is this parent controller to be loaded twice but have separated scopes ($scope.numberOfChildren === 1 in each child controller)
I managed to do this using ng-controller in the view template and deleting the $controller calls but I want to do it programmatically. (I don't want to have to write the same ng-controller code each time I am calling the directive).
<div ng-controller="parentController">
<first-directive></first-directive>
</div>
<div ng-controller="parentController">
<second-directive></second-directive>
</div>
Finally, to keep homogeneity in the code of the project, I'd rather not use the this and vm stuff to do the job if it possible.
parentController does NOT have its own scope, it operates on the $scope you're passing to it when you instantiate it this way $controller('parentController', {$scope: $scope}).
Checkout this simple demo fiddle.
The problem in your case might be caused by directives sharing the same scope and, thus, passing the same scope to the parent controller.
What you expect is exactly same with the way system run: scope is not sharing between two controller.
When you use a ng-controller in html, a new scope (controller instance) will be created. From your code above, two controller instance will be created. You can see it by adding {{$id}} to html and see id of scope instance.
<div ng-controller="parentController">
{{$id}}
<first-directive></first-directive>
</div>
<div ng-controller="parentController">
{{$id}}
<second-directive></second-directive>
</div>
If you see {{numberOfChildren == 2}} mean that your code is wrong in somewhere, not by sharing scope issue.

AngularJS inherit a single scope from another controller

Is there a way to inherit a single scope from one controller to another controller via $controller?
Here is my current code:
var peopleApp = angular.module('peopleApp', []);
peopleApp.controller('indexController', function($scope){
$scope.sampleVar = "Hello World";
$scope.sampleVar2 = "Hello World Again";
});
peopleApp.controller('addController', function($scope, $controller){
$controller('indexController', {$scope.sampleVar: $scope.sampleVar});
alert($scope.sampleVar);
});
I know this will throw an error because of "$controller('indexController', {$scope.sampleVar: $scope.sampleVar});" that should be "$controller('indexController', {$scope: $scope});" but the concept or desired return is there.
<body ng-controller="parentController">
<div ng-controller="childController1">
</div>
<div ng-controller="childController2">
</div>
</body>
You can inherit properties form parent controller to childController1 and childController2
Angularjs internally maintaines scope hierarchy via ng-controller directive where you defined in dom. For More detials in https://docs.angularjs.org/guide/controller
Another way is Inject $rootScope inside controller and access properties from
anywhere in your app controllers
var app = angular.module('sample',[])
app.controller('controller1',function($rootScope,$scope){
$rootScope.parent ="I am visible in any controller";
})
app.controller('controller2',function($rootScope,$scope){
//here you can access rootScope property using rootScope or Scope
// Because rootScope is parent for all scope object
// Angular scope protochain is like javascript protochain it looks from child to parent scope
console.log( $rootScope.parent);
console.log($scope.parent);
})

Angular scope variable is null when using ng-include (added a plunkr)

The model in the $scope isn't there when I try to access it in the save function.
As per the code below: desc has the value from the text input, however desc2 does not.
Is there anything wrong with the code, or am I missing something when it comes to the use of the $scope?
Html:
<input type="text" ng-model="myModel.description">
JavaScript
MyApp.controller('MyCtrl', ['$scope', function($scope) {
$scope.save = function() {
var desc = this.myModel.description; // This has a value
var desc2 = $scope.myModel.description; // This does NOT have a value
};
}]);
EDIT:
Added plunker: http://plnkr.co/edit/3l9ZE7O2wpbfnH7XPA9X?p=preview
This problem seem to be related to using ng-include.
If I don't use ng-include then it seem to work fine.
ng-include element introduces a child scope. Now your myModel is attached to a child, but your controller's $scope is the parent. Parent scopes can't see their children's variables, but children can see their parents'.
You can add myModel = {}; to your controller and it should work fine. That way the child scope references his parents' myModel and doesn't create his own.

Resources