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
Related
I have a utility controller build to manage documents attachments for reusing across my application.
<div ng-controller="someController">
<div ng-controller="documentController as temp1"></div>
<div ng-controller="documentController as temp2"></div>
</div>
Under the parent controller i.e. someController I have a broadcast method..
var module = angular.module("MyModule");
module.controller("someController",
function ($scope) {
$scope.$broadcast("callSomeFunctionInDocumentsController");
});
module.controller("documentController",
function($scope) {
$scope.$on("callSomeFunctionInDocumentsController", function() {
//do something here
});
});
Now the problem I am facing is that since the documentController is added twice to the view, the $on method is executed twice as well. Whereas based on some condition I would want to call the $on method either in temp1 or temp2 instance and not both.
I am not sure if what I wish to achieve is possible but any help will be much appreciated.
Thanks.
The $broadcast works simply: everyone who registered is notified through $on.
In your example, both controllers are registered.
So why do you use the same controller twice? Maybe worth to switch to component?
What about this one:
<div ng-controller="someController">
<div ng-controller="documentController as temp1"></div>
<div ng-if="oneCtrlGotNotification" ng-controller="documentController as temp2"></div>
</div>
where oneCtrlGotNotification is some flag (maybe under $rootScope).
So you will display second controller only when 1st already notified.
But it is a workaround.
One approach is to give a unique id to each element with a controller:
<div ng-controller="someController">
<div id="temp1" ng-controller="documentController as temp1"></div>
<div id="temp2" ng-controller="documentController as temp2"></div>
</div>
Then use the $attrs local to differentiate:
app.controller("documentController", function($scope, $attrs) {
$scope.$on("callSomeFunctionInDocumentsController", function() {
if ($attrs.id == "temp1") {
//do something specific to "temp1" controller
});
});
})
For more information, see
AngularJS Comprehensive Directive API Reference - controller
AngularJS $attrs Type API Reference
I want to update the scope inside a directive that is outside of my main view, here's my code:
index.html
<!-- the directive I want to update -->
<nav sitepicker></nav>
<section id="content-wrapper">
<!-- main content-->
<div ui-view></div>
</section>
sitepicker is essentialy just a dropdown menu that contains some html structure.
sitepicker.html
<span>{{currentWebsite}}</span> <-- this is the one I want to update
<ul>
<li ng-repeat="website in websites">{{website.name}}</li>
</ul>
and the JS:
.controller('sitepicker', function($scope, websiteService)
$scope.website = websiteService.currentWebsite; // not updating eventhough I update this in overview.js
});
overview.js
.controller('OverviewCtrl', function($scope, websiteService) {
websiteService.currentWebsite = website; // assume that this value is dynamic
});
but currentWebsite is not changing. How can I work around this? I want to avoid using $rootScope because I know it's bad.
Here's my service:
.factory('websiteService', function() {
var currentWebsite;
return {
currentWebsite: currentWebsite
};
});
Edit: Adding a watch like this works but i'm not sure if its good
.controller('sitepicker', function($scope, websiteService)
$scope.$watch(function() {
$scope.website = websiteService.currentWebsite;
});
});
Solution #1
We can add $watch to makes this possible
.controller('sitepicker', function($scope, websiteService)
$scope.$watch(function(){
return websiteService.currentWebsite;
}, function(newValue){
// Do something with the new value
});
});
Solution #2
We can also define websites in our main controller. Then, we can update it in our overview child controller like so:
.controller('sitepicker', function($scope, websiteService)
$scope.$parent.website = websiteService.currentWebsite;
});
it is important to use $parent
Since we update the controller that wraps up the whole app, we will be able to access it from anywhere, any directive, controller, view, etc.
From a server side code I am loading and angular template as follows:
<div ng-include="'/views/signup.html'" onload="init('premium')"></div>
The view is, for now, the following:
<form ng-controller="AccountSignupController as controller">
</form>
The controller is simply:
function AccountSignupController($scope) {
$scope.plan = '';
$scope.init = function (plan) {
$scope.plan = plan;
console.log(plan);
};
};
On the console plan is always "undefined".
How can I pass a value to the controller through the template?
Do I need to set the controller on ng-include div and use ng-init?
The scope of the controller is inside the form where 'ng-controller' is. Thus $scope.init function is only "available" inside that form.
Both directives should be at the same level. Try moving 'ng-controller' to the div or 'ng-init' to the form (if possible).
You are instantiating your controller using the controller as binding syntax. In that case properties and functions are bound to the this of your controller instead of $scope. Functions should be called using the binding.
Your template should be:
<div ng-include="'/views/signup.html'"
ng-controller="AccountSignupController as controller"
onload="controller.init('premium')"></div>
Your controller:
function AccountSignupController() {
var self = this;
this.plan = '';
this.init = function (initString) {
self.plan = initString;
console.log(initString);
};
};
For more information on the controller as binding syntax, see the AngularJS ngController API
I want to change ng-show in another controller than ng-show is.
myApp.controller('popupCtrl', function() {});
myApp.controller('changePopup', function($rootScope){
// now i wanna show my Ppopup
$rootScope.popup = true;
});
<div ng-controller="popupCtrl">
<div ng-show="popup">
Popuptext
</div>
</div>
But this doesn't work... How can I fix it?
Thanks!
So first thing, you should never add to the $rootScope or change it in anyway. It has been optimised by the angular team.
Second thing, there is no need to involve the $rootScope.
Here is a demo showing how to communicate across two controllers.
The key is the event aggregator pattern:
Communicator.register(function (newValue) {
vm.value = Communicator.value;
});
I created a function in the Communicator to register a callback function. The aim is that when a value gets changed the callback function is fired off. I.e. an event is triggered (change event).
The second key part is fire that change event off:
Communicator.change(!Communicator.value);
Here we pass through to the change function a new value which will do two things:
Update the internal value so we can keep track of it
Loop through all the registered callbacks and execute them passing in the new value.
By implementing this pattern, we can minimise the extent to which we communicate around our application ($rootScope can have a tendency to traverse the scope heirarchy when you $broadcast).
Now we can follow more closely the principle of single responsibility. Our class is aptly named in its current scope, when we look at this factory we can tell it is supposed to "communicate".
Finally, with a global event aggregator pattern ($rootScope) it is far more difficult to keep track of where these events are being broadcast from, and where they'll end up. Here we don't have that issue
One way to solve this is to use $rootScope.$broadcast
Here is an example: http://plnkr.co/edit/EmJnZvXFRWv6vjKF7QCd
var myApp = angular.module('myApp', []);
myApp.controller('popupCtrl', ['$rootScope', '$scope', function($rootScope,$scope) {
$scope.popup = false;
$rootScope.$on('changePopup', function(event, data) {
$scope.popup = !$scope.popup;
});
}]);
myApp.controller('changePopup', ['$rootScope', '$scope', function($rootScope, $scope) {
$scope.changePopup = function() {
$rootScope.$broadcast('changePopup', 'data could be sent here');
}
}]);
View:
<div ng-controller="popupCtrl">
<div ng-show="popup">
Popuptext
</div>
<div ng-controller="changePopup">
<button ng-click="changePopup()">Change the popup</button>
</div>
Using a service/factory is a better solution for cross controller communication if you are working on a large application, but for a smaller app I would say using $broadcast, $emit and $on is sufficient.
Here is a working demo for you - sorry I changed the controller names, but I am sure you will be able to build on this. Good luck
angular.module('myApp', [])
.controller('c1', function($scope) {
// now i wanna show my Ppopup
$scope.popup = false;
$scope.$on('popup', function() {
$scope.popup = true;
});
})
.controller('changepopup', function($rootScope, $scope) {
// now i wanna show my Ppopup
$scope.clicker = function() {
$rootScope.$broadcast('popup')
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="c1">
<div ng-show="popup">
Popuptext
</div>
</div>
<button ng-controller="changepopup" ng-click="clicker()">Click me</button>
</div>
I have two controllers where the first one contains a list of items:
$scope.items = [{id:1, desc:'desc1'},{id:2, desc:'desc2'}...];
The second one binds to a template which displays a list of items selected:
$scope.myitems = [1,2,3,...]; // ids only
<div ng-repeat="item in myitems">
{{item.id}} / {{item.desc}}
</div>
How can I look up the item desc in the ng-repeat of the second controller from the item list of the first controller?
Sharing data between controllers is best achieved via services/factories, an alternative is to use scope inheritance.
1. Factory/Service
angular.module('yourModule').factory('itemService', itemService);
function itemService() {
var items = [{id:1, desc:'desc1'},{id:2, desc:'desc2'}...];
return {
findItemById: findItemById
}
function findItemById(id) {
//logic to find item by id
}
}
Inject this factory in your controllers and add more functions if needed.
2. Scope inheritance
The key here is to nest your childcontroller, which I presume is the one with the ids.
<div ng-controller="topCtrl">
<div ng-controller="childCtrl">
<div ng-repeat="item in myitems">
{{item.id}} / {{item.desc}}
</div>
</div>
</div>
With this option, any controller that is nested within the topCtrl in the view has access to the topCtrl scoped variables.
A third option would be to store the data in the $rootScope, which is actually also a sort of scope inheritance (all scopes except for isolated directive scopes inherit from the rootScope), but that's probably not a good idea for your usecase.
You can try to use inheritance concept:
var app = angular.module(....);
app.controller('FirstCtrl ', function($scope) {
$scope.items = [{id:1, desc:'desc1'},{id:2, desc:'desc2'}...];
});
app.controller('SecondCtrl', function($scope, $controller) {
$controller('FirstCtrl', {$scope: $scope}); // $scope.items now available
});
Advantage over the $rootScope solution:
second controller will always have access to items, even if first controller is not instantiated
items array may be changed only by these two controllers, and not anyone other
Main flaw of this approach is that SecondCtrl will have access to any scope variable of the FirstCtrl and not only items array
EDIT. IMHO Factory/Service approach mentioned by #NexusDuck is the best one (composition over inheritance).
Can you assign values to $rootScope.items in first controller, then try to access in second controller.
You can use $emit and $on.
EX:
In First controller add
$scope.$emit('eventName', $scope.items);
};
And also pass $scope.items.
In Second Controller Add
$scope.$on('eventName', function (event, args) {
$scope.items = args;
});
args hold $scope.items value and is assign to $scope.items in second controlle, so now ng-repeat display value of $scope.items of First Controller.