AngularJS inherit a single scope from another controller - angularjs

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);
})

Related

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.

Why does not work two controller in Angular JS?

I have controller is named "UserController" in top of page:
<div ng-controller="UserController"><input type="text" ng-model="search"></div>
Also the same controller in bottom page from directive ng-view:
<div class="bottom" ng-controller="UserController">{{search}}</div>
Why I dont get value {{search}} in bottom part, when I fill field input in top?
Can I use one controller two times in a page?
Yes, you can use two controllers in AngularJs, Here is a demo.
What happens when I use ng-controller?
When you add ng-controller to a DOM element, angular create an instance of controller function and attaches it with that DOM, and thats why there is no two way data-binding between those divs.
How can I use data binding to share data between controllers?
You can use $rootScope variable or you can use services.
you can create service and inject in controller as dependency, so you can access its property with two way binding feature.
As said by JB Nizet, you need to have everything in the same "div".
<div ng-controller="UserController">
<input type="text" ng-model="search">
<div id="search-query">{{search}}</div>
</div>
Having the search-query at the bottom of the page is a matter of CSS, not Angular.
Controllers are not singletons. You have one controller for the top div, a second controller for the second div. One scope for the top div, one scope for the bottom div.
Both controllers have the same name, but you are ultimatally calling you controller function twice.
Some options you might want to consider to solve your problem:
Option 1) Use parent scope.
ng-model="$parent.search"
{{$parent.search}}
Option 2) Use root scope.
ng-model="$root.search"
{{$root.search}}
Option 3) Store the value in a service.
Services are singletons. If you type myService.search = $scope.search, then that value can read from the other controller.
You wont be able to watch a service variable, so perhaps you want to use the observer pattern here.
app.service("search", function() {
var listerners = [];
this.register = function(listener) {
listerners.push(listener);
};
this.update = function(searchValue) {
for(var i in listerners) {
listerners[i](searchValue);
}
};
});
app.controller("UserController", function($timeout, search){
search.register(function(searchValue) {
$timeout(function(){
$scope.search = searchValue;
});
});
$scope.$watch('search', function (newVal, oldVal, scope) {
search.update(newVal);
});
});
Option 4) Broadcast the new value.
$scope.$watch('search', function (newVal, oldVal, scope) {
$rootScope.$broadcast('search', newVal);
});
$scope.$on('search', function(event, data) {
$scope.search = data;
});
You can have multiple instances of the same controller in your page. They share the same functionality. But every instance of that controller is getting his own $scope. So in your first controller $scope.search can be 'mySearch', but the second controller won't get this, because it's another $scope.
You can do two things:
You can put the controller on a containing element, let's say the body, so both your input and your div are within the same $scope.
OR, if you want them to be seperate, you can use a service to share the search.
Your HTML:
<div ng-app="myApp">
<div ng-controller="UserController">
<input type="text" ng-model="search.mySearch"/>
</div>
<div ng-controller="UserController">
{{search.mySearch}}
</div>
</div>
Your Javascript:
var myApp = angular.module('myApp', []);
myApp.factory('Data', function(){
return { mySearch: '' };
});
myApp.controller('UserController', function( $scope, Data ){
$scope.search = Data;
});
See Fiddle

Accessing inherited scope with Controller As approach

With the original way to define controllers, accessing the parent's scope was fairly trivial, since the child scope prototypically inherits from its parent.
app.controller("parentCtrl", function($scope){
$scope.name = "Parent";
})
.controller("childCtrl", function($scope){
$scope.childName = "child of " + $scope.name;
});
<div ng-controller="parentCtrl">
{{name}}
<div ng-controller="childCtrl">
{{childName}}
</div>
</div>
The Controller-As approach seems to be the recommended way to declare a controller. But with Controller-As, the above approach no longer works.
Sure, I can access the parent scope with pc.name from the View:
<div ng-controller="parentCtrl as pc">
{{pc.name}}
<div ng-controller="childCtrl as cc">
{{cc.childName}}
</div>
</div>
I do have some issues with this (potential for spaghetti code), but this question is about accessing the parent scope from the child controller.
The only way I can see this working is:
app.controller("parentCtrl", function(){
this.name = "parent";
})
.controller("childCtrl", function($scope){
$scope.pc.name = "child of " + $scope.name;
// or
$scope.$parent.pc.name = "child of " + $scope.name;
// there's no $scope.name
// and no $scope.$parent.name
});
So now, the child controller needs to know about "pc" - except, this should (in my mind) be restricted to the view. I don't think a child controller should know about the fact that a view decided to declare a ng-controller="parentCtrl as pc".
Q: What's the right approach then?
EDIT:
Clarification: I'm not looking to inherit a parent controller. I am looking to inherit/change the shared scope. So, if I was to amend the first example, I should be able to do the following:
app.controller("parentCtrl", function($scope){
$scope.someObj = {prop: "not set"};
})
.controller("childCtrl", function($scope){
$scope.someObj.prop = "changed";
});
After researching, I came to the following realization:
Controller-As approach is NOT a substitute for using $scope. Both have their place, and can/should be used together judiciously.
$scope does exactly what the name implies: i.e. it defines ViewModel properties on the $scope. This works best for sharing scope with nested controllers that can use the $scope to drive their own logic or to change it.
Controler-As defines the entire controller object as a ViewModel with a named scope (via the controller's alias). This works best only in the View (but not other controllers), if the View decides if it wants to reference a specific controller ViewModel.
Here's an example:
var app = angular.module('myApp', []);
// Then the controllers could choose whether they want to modify the inherited scope or not:
app.controller("ParentCtrl", function($scope) {
this.prop1 = {
v: "prop1 from ParentCtrl"
};
$scope.prop1 = {
v: "defined on the scope by ParentCtrl"
};
})
.controller("Child1Ctrl", function($scope) {})
.controller("Child2Ctrl", function($scope) {
// here, I don't know about the "pc" alias
this.myProp = $scope.prop1.v + ", and changed by Child2Ctrl";
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js"></script>
<body ng-app="myApp">
<div ng-controller="ParentCtrl as pc">
<div ng-controller="Child1Ctrl">
<div>I know about the "pc" alias: {{pc.prop1.v}}</div>
</div>
<div ng-controller="Child2Ctrl as ch2">
<div>I only care about my own ViewModel: {{ch2.myProp}}</div>
</div>
</div>
You should do like :
html
<div ng-controller="ChildController as child">
<button type="button" ng-click="child.sayMe()">Say me!</button>
</div>
js
var app = angular.module('myApp', [])
app.controller('BaseController',function() {
this.me = 'Base';
this.sayMe= function() {
alert(this.me);
}
});
app.controller('ChildController', function($scope, $controller) {
var controller = $controller('BaseController as base', {$scope: $scope});
angular.extend(this, controller);
this.me = 'Child';
});
take a look at https://docs.angularjs.org/guide/controller
For anyone looking to simply access the parent scope programmatically, use the $scope service, find the parent scope, and access the name used for the parent scope's controllerAs, e.g.:
$scope.$parent.someName.doSomething();

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?
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>

Resources