AngularJS - update a directive outside the view - angularjs

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.

Related

Change ng-show in another controller?

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>

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

Dynamically add existing controller into another controller with AngularJS

in my app I have a wrapper controller that handles some properties dynamically based on other-controllers within it. everything works like a charm if the other-controllers are present/static on load, but as soon as I'm trying to make them dynamic, they stop working.
It was my understanding that the $rootScope is available from everywhere within the app, is that not true?
my JS looks like this:
var webApp = angular.module("webApp",[]);
webApp.controller("ControllerA", function($scope, $rootScope){
$rootScope.cnt = 0;
$rootScope.cntPlusPlus = function(){
$rootScope.cnt++;
};
$rootScope.controllerBs = [];
var template = $(".controller-b").html();
$scope.addControllerB = function(){
$rootScope.controllerBs.push(template);
};
});
webApp.controller("ControllerB", function($scope, $rootScope){
$scope.cntPlusPlus = function(){
console.log("overwrite plus plus");
}
});
Full example: http://plnkr.co/edit/tAcv1F9y7t9q9XsQ1EFL?p=preview
I know that this would be probably better with directives, but is there any way to make it work with Controllers?
thanks for the help
Don't try to access the DOM from controller code. Never. It is very bad practice which breaks AngularJS conventions and eventually provides you with bad architecture. This also means you should not create any DOM elements manually from a controller.
Better to manipulate with the scope itself, not with its visual representation. You can add new models to scope on your button's click, which will be translated to new elements by ng-repeat directive, each with its own controller (remember controllers are instances, not singletons, so that they have separated life cycles).
You might want to make use of <script type="text/ng-template"> and ng-include here instead of hidden divs.
Try to avoid using $rootScope when possible - it is global state which can be dangerous.
It might look like this then (plunker):
HTML:
<div class="controller-a" ng-controller="ControllerA">
Controller A
<div>
<button ng-click="cntPlusPlus()">cnt++</button> CNT: {{cnt}}
</div>
<button ng-click="addB()">Add B</button>
<div ng-repeat="B in Bs">
<div ng-include="'b-template'"></div>
</div>
</div>
<script type="text/ng-template" id="b-template">
<div ng-controller="ControllerB">this is controller b: <button ng-click="cntPlusPlus()">cnt++</button></div>
</script>
JS:
var webApp = angular.module("webApp",[]);
webApp.controller("ControllerA", function($scope){
$scope.cnt = 0;
$scope.cntPlusPlus = function(){
$scope.cnt++;
};
$scope.Bs = [];
$scope.addB = function(){
$scope.Bs.push({});
};
});
webApp.controller("ControllerB", function($scope){
$scope.cntPlusPlus = function(){
console.log("overwrite plus plus");
$scope.$parent.$parent.$parent.cnt++; //should be moved to service
}
});
</script>

Angular: How do you associate a controller and a DOM element explicitly

I have a DOM element that I want to associate with a controller, the usual way of course is using ng-controller. But in this case I cannot do this (for reasons that will take too long to explain).
Is there a way to do this association manually?
e.g.
<div id="foo">
...
</div>
angular
.module('APP', [])
.controller('FooCtrl', function ($scope) {
...
});
var $ele = $('#foo');
angular.bootstrap($ele, ['APP']);
// somehow associate #foo with FooCtrl without using ng-controller="FooCtrl"
Is this possible?
Thanks
You could try with a directive.
angular.module('APP').directive('fooCtrl', function () {
return {
controller: 'Foo'
};
});
then in your HTML
<div id="foo" foo-ctrl>

Angularjs different controllers for one include

I want to have element where i can have 2 views using their own controller but only one at a time.
I can't use a ng-view and use the routeProvider because in the future I need to include more ng-includes that need to change their content depending on the possible actions.
I created a fiddle http://jsfiddle.net/EvHyT/29/.
So I used a ng-include and then I set the src for it from a main controller. At that point I want to use controller 1 or controller 2.
function MainCtrl($rootScope, $scope, navService){
$scope.template = {};
$scope.loadCtrl1=function(param){
navService.loadCtrl1(param);
}
$scope.loadCtrl2=function(param){
navService.loadCtrl2(param);
}
$rootScope.$on('loadCtrl1', function(e, args){
$scope.template = {'url': 'temp1'};
});
$rootScope.$on('loadCtrl2', function(e, args){
$scope.template = {'url': 'temp2'};
});
}
I use a service for communication because i want to move the load controller functions in a child controller.
var myApp = angular.module('myApp',[]);
myApp.factory('navService', function($rootScope) {
return {
loadCtrl1:function(param){
$rootScope.$broadcast('loadCtrl1', {'id':param});
},
loadCtrl2:function(param){
$rootScope.$broadcast('loadCtrl2', {'id':param});
}
};
});
I know this solution is bad because the controllers are not yet created when a different template is inserted so my event listener will not fire. Also can I destroy the previous instances of the controller because switching between the two controllers makes my event fire multiple times.
function Child1Ctrl($scope, $rootScope){
$rootScope.$on('loadCtrl1', function(e, args){
alert(args.id);
});
}
function Child2Ctrl($scope, $rootScope){
$rootScope.$on('loadCtrl2', function(e, args){
alert(args.id);
});
}
You don't need to broadcast (and shouldn't be broadcasting) to make this happen.
In my experience, if you're broadcasting on the rootScope, you're probably working too hard.
A simpler way of loading the template is very similar to what you're doing:
my.NavService = function() {
this.template = 'index.html';
this.param = null;
};
my.NavService.prototype.setTemplate(t, p) {
this.template = t;
this.param = p;
};
my.ctrl = function($scope, nav) {
$scope.nav = nav;
$scope.load = function(t, p) {
nav.setTemplate(t, p);
};
};
my.ctrl1 = function($scope, nav) {
$scope.param = nav.param;
};
my.ctrl2 = function($scope, nav) {
$scope.param = nav.param;
};
module.
service('nav', my.NavService).
controller('mainCtrl', my.ctrl).
controller('ctrl1', my.ctrl1).
controller('ctrl2', my.ctrl2);
<script type="text/ng-template" id="temp1.html">
<div ng-controller="ctrl1">Foo {{param}}.</div>
</script>
<script type="text/ng-template" id="temp2.html">
<div ng-controller="ctrl2">Bar {{param}}.</div>
</script>
<div ng-controller="mainCtrl">
<a ng-click="load('temp1.html', 16)">Load 1</a>
<a ng-click="load('temp2.html', 32)">Load 2</a>
<div ng-include src="nav.template"></div>
</div>
A setup like this would work much better for you.
Alternatively, you should look into selectively showing elements with ng-switch. Unlike ng-show/hide, ng-switch does not simply add "display:none" to the CSS. It removes it from the DOM.
Some notes:
The above example may not be a working example, it's a demonstration of the idea.
A working example can be seen here: http://jsbin.com/ofakes/1 It modifies your original code.
JSFiddle had some issues with loading the include from the on page script
tag.
JSBin was a little better.
I didn't really get it to work as expected until I broke out the templates
into their own files.

Resources