Child controller not inheriting from parent controller - angularjs

I've been having a lot of trouble with understanding scope inheritance and I've tried my best to pass data objects from a parent controller into the child controller, but I can't seem to get things to work. Can someone explain why this isn't functioning? Thank you!
EDIT: I didn't specify this earlier, but it's a project requirement to use the John Papa style guide, so I can't solve this problem by using $scope in either of the controllers.
UPDATE: It seems I misunderstood the purpose of using this... based on help from posters below, I now understand that certain actions require the use of $scope and John Papa's style guide simply asks developers to use this when appropriate to avoid scope conflicts, not as a replacement for scope
JS
//parent.controller.js
(function () {
'use strict';
angular
.module('app')
.controller('ParentController', ParentController);
ParentController.$inject = ['$scope'];
function ParentController($scope) {
var vm = this;
console.log(this);
vm.test = {};
vm.test.label = "This is being set in the parent controller.";
}
})();
//child.controller.js
(function () {
'use strict';
angular
.module('app')
.controller('ChildController', ChildController);
ChildController.$inject = ['$scope'];
function ChildController($scope) {
var vm = this;
vm.test = vm.test;
}
})();
HTML
<div ng-controller="ParentController as vm">
<div>PARENT: {{vm.test.label}}</div>
<div ng-controller="ChildController as vm">
<div>CHILD: {{vm.test.label}}</div>
</div>
</div>
RESULT
PARENT: 'This is being set in the parent controller.'
CHILD:

Issue is: vm is also the part of the $scope itself. So you can not have same this instance for Parent & Child controller. Otherwise you will be facing issues while using them.
If you want to isolate this instance for Parent & Child then give different names.
Since vm is also the part of the controller so if you want to access Parent's vm inside Child controller then you will have to do $scope.vm
Working code as per your requirement is attached below:
Controller
---------
(function () {
'use strict';
angular
.module('app', [])
.controller('ParentController', ParentController);
ParentController.$inject = ['$scope'];
function ParentController($scope) {
var vm = this;
console.log(this);
vm.test = {};
vm.test.label = "This is being set in the parent controller.";
}
})();
(function () {
'use strict';
angular
.module('app')
.controller('ChildController', ChildController);
ChildController.$inject = ['$scope'];
function ChildController($scope) {
var childVm = this;
childVm.test = $scope.vm.test;
}
})();
HTML
---
<div ng-app="app">
<div ng-controller="ParentController as vm">
<div>PARENT: {{vm.test.label}}</div>
<div ng-controller="ChildController as childVm">
<div>CHILD: {{childVm.test.label}}</div>
</div>
</div>
</div>
Cheers!

After checking the plnkr,hope I was able to understand your question, and giving answer based on it:
In controller file, for ChildController
var childCtrl = this;
// Why don't either of these work?
// childCtrl.test = parentCtrl.test;
// childCtrl.test = this.parentCtrl.test;
assigning parentCtrl.test, doesn't makes any sense, as it is the object of the parent controller.
this.parentCtrl.test will get evaluated to childCtrl.parentCtrl.test which is invalid.
Following worked because-
// But this does...
childCtrl.test = $scope.parentCtrl.test;
During the code execution
Two separate scopes will get created each for ParentController and ChildController.
child will inherit the properties of parent, those which are not present in childController and will be assigned to the childController scope.
Due to which you where able to access value in child with scope.

Related

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.

Controller inheritance with injection

I'm having a hard time trying to understand the best way to achieve inheritance with my controllers. I've seen a few other posts here about these but I still donĀ“t get some things.
Here's what I have:
- 2 controllers which are 80% similar. I already have a factory which both use to get the data which will be displayed.
- I use the controllerAs notation, with var vm = this
- there's a mix of vars and functions which will be used in the view and therefore are created inside vm, and some other internal vars and functions which are not.
- so I tried to create a single parent controller with all this and then use injection to create these 2 controllers, overwriting only what I need, but this is not working as I expected and also I'm not sure this is the right thing to do
Here is a simplified version of the code.
(function() {
angular
.controller('ParentController', ParentController)
ParentController.$inject = [ '$scope', '$location' ];
function ParentController($scope, $location) {
var vm = this; // view model
var URL = $location.url();
var isDataLoaded = false;
vm.predicate = 'order';
vm.reverse = false;
vm.results;
vm.isDataReady = isDataReady;
vm.setOrder = setOrder;
function isDataReady() {
return isDataLoaded;
}
function setOrder(p) {
vm.reverse = (p === vm.predicate) ? !vm.reverse : false;
vm.predicate = p;
}
$scope.$on('READ.FINISHED', function() {
isDataLoaded = true;
})
}
})();
-
(function() {
angular
.controller('ChildController', ChildController)
ChildController.$inject = ['$controller', '$scope', 'myFactory'];
function ChildController($controller, $scope, myFactory) {
$controller('ParentController', {$scope: $scope});
var TEMPLATE = 'SCREEN';
// ************** M A I N **************
myFactory.getResults(URL, vm);
}
})();
This is now working as I expected.
When I inject ChildController with ParentController, do I really need to inject the $scope? I'm actually using vm. Also, do I need to inject also $location? In this example when I execute my code I'm forced to use var URL = $location.url(); again in my ChildController, I expected to inherite the value from ParentController.
So the thing is, am I only getting values from $scope if I work like this? what about vm? and what about those vars/functions declared outside vm like var isDataLoaded?
I'd appreciate some insight about this. Would this be the right way to do it?
Many thanks.
EDIT: Ok, I found out how to use my ControllerAs syntax with this. Code in the child controller would be like this:
function ChildController($controller, $scope, myFactory) {
$controller('ParentController as vm', {$scope: $scope});
var vm = $scope.vm;
var TEMPLATE = 'SCREEN';
// ************** M A I N **************
myFactory.getResults(URL, vm);
}
But I still need to get a way to also recover the regular var/functions inside the parent controller. Any ideas? Can it be done cleanly?
I recommend you to implement usual javascript's inheritance mechanism between two classes. At ChildController constructor you will execute ParentController constructor and pass to it injected parameters (without using $controller service).
I created simple example (very far from your logic but consider it as patten). I have two controllers: ParentController and ChildController, that inherited from first one. I used only ChildController with $scope, $interval and custom service with name "myservice" (all of them needed only for example). You can see that I used methods and fields from parent and child controllers. Logic of my app is very simple: you can add new items to collection (by means of ParentController) and remove them(by means of ChildController) with logging.
At that case I recommend you use ChildController as ctrl for Data Binding instead of $scope, because it more in line inheritance paradigm (we inherite controllers(ctrl) not $scope).
P.S. If you be going to use inheritance very often I recommend you to use TypeScript - it gives very simple and flexible solution of this problem in c# style.
Controllers.js
function ParentController(myservice, $scope, $interval) {
this.newOne={};
this.lastacivity={};
this.$interval = $interval;
this.items = myservice();
}
ParentController.prototype.Log = function(item){
var self = this;
this.$interval(function(){
console.log(item);
self.lastacivity = item;
}, 100, 1);
}
ParentController.prototype.AddNew = function (Name, Age) {
var newitem = {
name: this.newOne.Name,
age: this.newOne.Age
}
this.items.push(newitem);
this.Log(newitem);
}
function ChildController(myservice, $scope, $interval) {
//Transfering myservice, $scope, $interval from ChildController to ParentController constructor,
//also you can pass all what you want: $http, $location etc.
ParentController.apply(this, [myservice, $scope, $interval]);
}
ChildController.prototype = Object.create(ParentController.prototype)
//your ChildController's own methods
ChildController.prototype.Remove = function (item) {
this.items.splice(this.items.indexOf(item), 1);
this.Log(item);
}
script.js
(function(angular) {
'use strict';
angular.module('scopeExample', []).factory('myservice', function() {
var items = [
{name:"Mike",age:21},
{name:"Kate",age:22},
{name:"Tom",age:11}
];
return function(){
return items;
}
}).controller('ChildController', ['myservice', '$scope', '$interval', ChildController]);
})(window.angular);
HTML
<body ng-app="scopeExample">
<div ng-controller="ChildController as ctrl">
<label>Name</label>
<input type='text' ng-model='ctrl.newOne.Name'/>
<label>Age</label>
<input type='text' ng-model='ctrl.newOne.Age'/>
<input type='button' value='add' ng-click='ctrl.AddNew()' ng-disabled="!(ctrl.newOne.Name && ctrl.newOne.Age)"/>
<br>
<br>
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in ctrl.items">
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td>
<input type='button' value='X' ng-click='ctrl.Remove(item)'/>
</td>
</tr>
</tbody>
</table>
<p>{{ctrl.lastacivity | json}}</p>
</div>
</body>

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

angular.js controller as syntax template binding using $template cache service

i have already used angular js before but now i am using controller as syntax in angular js and i am not able to bind template.
My controller code:
(function () {
angular.module("vkApp")
.controller("Feeds", Feeds);
function Feeds(FeedSetting, FeedLoader, $templateCache, $compile) {
var vm = this;
FeedSetting.initializeSetting(vm);
//functions declaration
vm.addFeed = addFeed;
// function implementations
function addFeed(text) {
return FeedLoader.loadFeed("http://rss.cnn.com/rss/edition_world.rss")
.then(function (feedData) {
console.log(feedData.data.responseData.feed);
vm.feedList = feedData.data.responseData.feed;
var feedTemplate = $templateCache.get("feedTemplate");
feedTemplate.then(function (markup) {
$compile(markup.data)(vm).appendTo(angular.element("#FeedArea"));
});
return vm.feedList;
});
}
};
})();
My template is:
<h7>{{feed.feedList.title}}</h7>
feed.html page:
<div id="rightSide" ng-controller="Feeds as feed">
<div class="news-feed-wrapper" id="FeedArea">
</div>
</div>
when the binding is performed it gives me error in console
You need to make couple of changes
1)Replace this line
$compile(markup.data)(vm).appendTo(angular.element("#FeedArea"));
with
$compile(markup.data)($scope).appendTo(angular.element("#FeedArea"));
because behind the scene your custom variable vm bind with Angular $scope. So $compile would work the same like it was using classic controller with $scope syntax
2) And in your binding replace
<h7>{{feed.feedList.title}}</h7>
with
<h7>{{vm.feedList.title}}</h7>
3) And inside Feed.html
ng-controller="Feeds as feed"
should be
ng-controller="Feeds as vm"
After the above changes it should work.

AngularJS Modules/Scope Sharing

I recently started using AngularJS and the way I'm building my apps now is like this:
MainController.js
var app = angular.module('app', ['SomeController', 'MainController']);
app.controller('MainController', function ($scope) {
// do some stuff
}
SomeController.js
var SomeController= angular.module('SomeController', []);
SomeController.controller('SomeController', function ($scope) {
$scope.variable = "test";
// do some otherstuff
}
The problem that Im' running into is that the scope is not being shared between modules. From MainController I can't get the variable "test" for example.
What is the best practice for this? Do I store all my controllers in 1 module in 1 file?
How can i have 1 page with 2 controllers and share the $scope between them, or is it OK to put everything in just one controller ?
You could use a service like this: Live demo here (click).
JavaScript:
var otherApp = angular.module('otherApp', []);
otherApp.factory('myService', function() {
var myService = {
someData: ''
};
return myService;
});
otherApp.controller('otherCtrl', function($scope, myService) {
$scope.shared = myService;
});
var app = angular.module('myApp', ['otherApp']);
app.controller('myCtrl', function($scope, myService) {
$scope.shared = myService;
});
Markup:
<div ng-controller="otherCtrl">
<input ng-model="shared.someData" placeholder="Type here..">
</div>
<div ng-controller="myCtrl">
{{shared.someData}}
</div>
Here's a nice article on sharing data with services.
You can also nest controllers to have the parent controller's scope properties inherited by the child scope: http://jsbin.com/AgAYIVE/3/edit
<div ng-controller="ctrl1">
<span>ctrl1:</span>
<input ng-model="foo" placeholder="Type here..">
<div ng-controller="ctrl2">
<span>ctrl2:</span>
{{foo}}
</div>
</div>
But, the child won't update the parent - only the parent's properties update the child.
You would use "the dot rule" to have updates on the child affect the parent. That means nesting your properties in an object. Since the parent and child both have the same object, changes on that object will be reflected in both places. That's just how object references work. A lot of people consider it best practice to not use inheritance, but put everything in directives with isolated scope.
You can use $rootScope, each Angular application has exactly one root scope.
Reference
app.controller('MainController', function ($scope, $rootScope) {
$rootScope.data = 'App scope data';
}

Resources