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';
}
Related
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.
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>
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
I'm trying to understand why I must use the as in order that the two-way binding will work with this inside a controller.
working example:
<div ng-controller="MyController as TestController">
{{TestController.test()}}
</div>
<script>
var app = angular.module('myApp', []);
app.controller('MyController', function(){
this.test = function test(){
return "test";
};
});
</script>
not working example:
<div ng-controller="MyController">
{{MyController.test()}}
</div>
<script>
var app = angular.module('myApp', []);
app.controller('MyController', function(){
this.test = function test(){
return "test";
};
});
</script>
If you want to use this in your controllers you need to use the controller as syntax otherwise you have to use $scope in your controllers. If you didn't use controller as the controller would need to be:
app.controller('MyController', function($scope){
$scope.test = function test(){
return "test";
};
});
and the view would need to be:
<div ng-controller="MyController">
{{test()}}
</div>
One of the benefits of the controller as syntax is it helps to promote the use "dotted" object in the View which helps to avoid any reference issues that may occur without "dotting". For more info on scope reference issues take a look at this post
Not really an answer to your question, but normally you'd define functions you want to invoke from the DOM on the Controller's $scope.
Example:
<div ng-controller="MyController">
{{test()}}
</div>
<script>
var app = angular.module('myApp', []);
app.controller('MyController', function($scope){
$scope.test = function test(){
return "test";
};
});
</script>
http://plnkr.co/edit/lbgG9MCJ1kNBhArLpEpc?p=preview
Edit: Sorry, forgot to update the code in my post. The plnkr should've been right all along though.
Thanks to Wayne Ellery:
It's because Angular added the controller as syntax in 1.2 which enables you to work with this. ng-controller="MyController as myController". Think of it as var myController = new MyController();. It's essentially scoping an instance of MyController to myController.
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();