Not able to figure out what the bug in this code is.I've tried to only post the relevant parts of the code here.
Controller
myApp.controller('MessageCtrl', function ($scope, notificationService, $rootScope) {
$scope.notificationService = notificationService;
$scope.msgCount = 0;
$scope.notificationService.subscribe({channel : 'my_channel'});
$rootScope.$on('pubnub:msg',function(event,message){
$scope.msgCount = $scope.msgCount + 1;
//$scope.$digest();
});
});
My Notification Angular Service
myApp.factory('notificationService',['$rootScope', function($rootScope) {
var pubnub = PUBNUB.init({
publish_key : '..',
subscribe_key : '..'
});
var notificationService = {
subscribe : function(subscription) {
pubnub.subscribe({
channel : subscription.channel,
message : function(m){
$rootScope.$broadcast('pubnub:msg', m);
}
});
}
};
return notificationService;
}]);
And the template :
<div>
Count = {{msgCount}}
</div>
The problem :
Using console logs & using karma tests I have confirmed that the $rootScope.$on method in MessageCtrl is getting called when I do a $broadcast from Notification Service. And that the msgCount variable is getting incremented. However, I don't see the updated value being reflected in the template without running a $scope.$digest() . I am pretty sure I shouldn't be needing to have to call $scope.$digest , ie Angular should be providing me this binding.
Interestingly, when I tried a $rootScope.$broadcast from another controller, the msgCount in the template got incremented without having to call $scope.$digest().
Can anyone kindly help me here. Thank you.
Update
Thanks to Peter and looking at the google group discussion, wrapping the $broadcast in an $apply did the trick.
$rootScope.$apply(function(){
$rootScope.$broadcast('pubnub:question', m);
});
It seems that your $broadcast happens outside AngularJS and you need to notify your app about it with calling $apply(), but better do it in the notificationService.
As for $broadcast and $on trigger a apply/digest you can read in this post. Brief overview of AngularJs source files make me sure that $broadcast does not auto-apply changes (look here ). $broadcast just calling listeners and nothing else.
Please, take a look at this simple example on jsFiddle .
The template
<div ng-controller="myCtrl">
<p>Count: {{ count }}</p>
<button ng-click="fireEvent()">Fire Event</button>
</div>
The controller
angular.module("app", [])
.controller('myCtrl', function($scope, $rootScope, notificationService) {
$scope.count = 0;
notificationService.subscribe();
$rootScope.$on('event', function() {
console.log("event listener");
$scope.count++;
});
$scope.fireEvent = function() {
// it is ok here due to ngClick directve
$rootScope.$broadcast('event', true);
};
})
And factory
.factory('notificationService',['$rootScope', function($rootScope) {
var notificationService = {
subscribe : function() {
setInterval(function(){
console.log("some event happend and broadcasted");
$rootScope.$broadcast('event', true);
// angular does not know about this
//$rootScope.$apply();
}, 5000);
}
};
return notificationService;
}]);
Of course in both cases you will see that event listener fires, but ngClick fires $digest and your notificationService does not.
Also you can get some info about sources that will start the digest cicle in this nice answer https://stackoverflow.com/a/12491335/1274503
Related
hi all i am using angulrajs passing one value from one controller to another controller using service it's work fine but my need is when service value change in controller 2 i get the service value in one scope when scope value change i need trigger the function it's called refresh function when service value change and that i need to call the refresh function here my fiddle
https://jsfiddle.net/ctawL4t3/10/
You can just $watch your value.storeObject. Though it's not best of the practices, but it suits this kind of feature.
$scope.$watch('value.storedObject', function(newVal) {
if(newVal !== '') {
refresh()
}
})
working fiddle (open console to see refresh function logging)
You can try to use angular default $emit, $broadcast, or try to do 2 simple functions in own service
angular.module('app').factory('StoreService', function() {
var listeners = {};
var emit = function(name, val) {
if(listeners[name]) {
listeners[name](val)
}
}
var on = function(name, callback) {
listeners[name] = callback;
}
return {
emit: emit,
on: on,
storedObject: ''
};
});
JSFiddle example
JSFiddle example $watch
JSFiddle example ng-change is better because, you can use easily debounce
you can use broadcast function for that
Please check this SO link to find the related answer
How to call a function from another controller in angularjs?
app.controller('One', ['$scope', '$rootScope'
function($scope) {
$rootScope.$on("CallParentMethod", function(){
$scope.parentmethod();
});
$scope.parentmethod = function() {
// task
}
}
]);
app.controller('two', ['$scope', '$rootScope'
function($scope) {
$scope.childmethod = function() {
$rootScope.$emit("CallParentMethod", {});
}
}
]);
When working on a project, as these things tend to happen, we came across a situation where we were stumped on how to update certain UI elements when other things were done. For example, the navigation contains a counter of how many pending activities are due today. At any point in time during usage of the app, a user might schedule an activity for later today, and the count section would need to call the API to generate a count and the drop-down items associated with it.
How can I make a navigation controller pull the new list of activities when the main controller makes a change?
See this code for an example.
<div ng-app="TestApp">
<nav ng-controller="navigationController">
<p>The navigation count is: {{items.length}}</p>
</nav>
<div ng-controller="mainController">
<p>The main count is: {{items.length}}</p>
<p>
<button ng-click="addItem()" type="button">Add item.</button>
</p>
</div>
</div>
<script>
var app = angular.module('TestApp', []);
app.factory("api", function() {
return {
update: function() {
return ['a', 'b', 'c', 'd', 'e'];
}
};
});
app.factory("sharedFactory", function(api) {
var obj = {};
obj.items = ["a"];
obj.update = function() {
obj.items = api.update();
};
return obj;
});
app.controller("mainController", function(sharedFactory, $scope) {
$scope.items = sharedFactory.items;
$scope.addItem = function() {
sharedFactory.update();
};
});
app.controller("navigationController", function(sharedFactory, $scope) {
$scope.items = sharedFactory.items;
});
</script>
Our current solution was to create a callback service that other controllers could subscribe to, and then when an activity was created have those callbacks run as needed. This works nicely, but I'm nervous that I'm "doing it wrong".
We're switching to the Angular UI Router, now, so I'm curious if there's a better way of doing so in it. Right now our navigation handler is a stateless controller that hooks into our callback service still.
A nice way to handle this could be to use $scope.$on to listen for events, and $scope.$emit to fire an event going up the scope or $scope.$broadcast to fire an even going down the scope.
In each piece of the UI that needs to be updated can be listening with $scope.$on and update itself when an event is fired, like your user scheduling an event for later today.
Angular docs for $on, $emit and $broadcast
Though I generally think that registering scope values on a controller with a service is the best way to accomplish another option would be to use a factory and set a property of that on scope.
angular.module('app').factory('myService', function() {
var myService = {};
service.count = 0;
/// other service functions
return myService;
}
angular.module('app').controller('myController', function(myService) {
this.count = myService.count;
}
However you feel about MVC, you could use angular's internals to automatically do this:
https://jsfiddle.net/gkmtkxpm/
var myApp = angular.module('myApp', []);
myApp.factory('counter', function() {
return {
count: 0
};
});
myApp.controller('CounterController', function (counter) {
var vm = this;
vm.counter = counter;
vm.increment = function() {
vm.counter.count = vm.counter.count + 1;
};
});
edit:
Concerning your updated question, see the updated fiddle:
https://jsfiddle.net/gkmtkxpm/1/
In my application, I'm getting some data in app.run using $http.get()
The result from this is required to proceed to the controller.
Currently my controller is executing before the completion of this $http.get
How can I make my controller's execute after the execution of $http.get()
app.run(function ($rootScope, $http, $cookies) {
var currentLanguageId = angular.fromJson($cookies.get('mykey')).UserInfo.Country;
$http.get('myurl').then(function (serviceInfo) {
$rootScope.serviceURL = serviceInfo.data.serviceURL;
}, function (error) {
alert('error service info');
});
run can't handle asynchronous work at the moment. See this issue : https://github.com/angular/angular.js/issues/4003
You have multiple solutions for that. You can get your data without Angular and start Angular manually ( How to wait for a promise in a Run block in Angular? )
Or you can use the resolve of your route to do this : AngularJS : Initialize service with asynchronous data
You can use angular scope event. When data is fetched, you can broadcast an event from $rootScope to all controllers and receive this event in target controller.
You may use $rootScope.$broadcast();in the app.run and then use $rootScope.$on() in your controller.
app.run(function ($rootScope, $http, $cookies) {
var currentLanguageId = angular.fromJson($cookies.get('mykey')).UserInfo.Country;
$http.get('myurl').then(function (serviceInfo) {
$rootScope.serviceURL = serviceInfo.data.serviceURL;
$rootScope.$broadcast('serviceInfoReceived')
}, function (error) {
alert('error service info');
});
});
In your Controller
app.controller ('myCtrl' , function($rootScope) {
$rootScope.$on("serviceInfoReceived", function(){
console.log($rootScope.serviceURL)
});
})
Hope this may help you.
This is an old question, however the $rootScope.$on solution will not always work. It will depend on the timing of the controller registering the listener. The way I have found that works is to set a $rootScope property, and then configure a recursive timeout to wait for it to be set.
function waitForRun() {
if($rootScope.runFinalized){
// do something
} else {
$timeout(waitForRun, 500)
}
}
waitForRun();
and after the last .run block:
.run(function($rootScope) { $rootScope.runFinalized = true; })
Ugly, but it works.
I am trying to call a method of second controller in first controller by using scope variable. This is a method in my first controller:
$scope.initRestId = function(){
var catapp = document.getElementById('SecondApp');
var catscope = angular.element(catapp).scope();
catscope.rest_id = $scope.user.username;
catscope.getMainCategories();
};
I am able to set the value of rest_id but I cannot call getMainCategories for some reason. The console shows this error:
TypeError: Object # has no method 'getMainCategories'
Is there a way to call the above method?
Edit:
I used the following approach to load two apps at the same time;
angular.bootstrap(document.getElementById('firstAppID'), ['firstApp']);
angular.bootstrap(document.getElementById('secondAppID'), ['secondApp']);
I could definitely use a service here, but I wanted to know if there are any other options to do the same!
The best approach for you to communicate between the two controllers is to use events.
Scope Documentation
In this check out $on, $broadcast and $emit.
In general use case the usage of angular.element(catapp).scope() was designed for use outside the angular controllers, like within jquery events.
Ideally in your usage you would write an event in controller 1 as:
$scope.$on("myEvent", function (event, args) {
$scope.rest_id = args.username;
$scope.getMainCategories();
});
And in the second controller you'd just do
$scope.initRestId = function(){
$scope.$broadcast("myEvent", {username: $scope.user.username });
};
Edit: Realised it was communication between two modules
Can you try including the firstApp module as a dependency to the secondApp where you declare the angular.module. That way you can communicate to the other app.
Here is good Demo in Fiddle how to use shared service in directive and other controllers through $scope.$on
HTML
<div ng-controller="ControllerZero">
<input ng-model="message" >
<button ng-click="handleClick(message);">BROADCAST</button>
</div>
<div ng-controller="ControllerOne">
<input ng-model="message" >
</div>
<div ng-controller="ControllerTwo">
<input ng-model="message" >
</div>
<my-component ng-model="message"></my-component>
JS
var myModule = angular.module('myModule', []);
myModule.factory('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.message = '';
sharedService.prepForBroadcast = function(msg) {
this.message = msg;
this.broadcastItem();
};
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
By the same way we can use shared service in directive. We can implement controller section into directive and use $scope.$on
myModule.directive('myComponent', function(mySharedService) {
return {
restrict: 'E',
controller: function($scope, $attrs, mySharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'Directive: ' + mySharedService.message;
});
},
replace: true,
template: '<input>'
};
});
And here three our controllers where ControllerZero used as trigger to invoke prepForBroadcast
function ControllerZero($scope, sharedService) {
$scope.handleClick = function(msg) {
sharedService.prepForBroadcast(msg);
};
$scope.$on('handleBroadcast', function() {
$scope.message = sharedService.message;
});
}
function ControllerOne($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'ONE: ' + sharedService.message;
});
}
function ControllerTwo($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'TWO: ' + sharedService.message;
});
}
The ControllerOne and ControllerTwo listen message change by using $scope.$on handler.
Each controller has it's own scope(s) so that's causing your issue.
Having two controllers that want access to the same data is a classic sign that you want a service. The angular team recommends thin controllers that are just glue between views and services. And specifically- "services should hold shared state across controllers".
Happily, there's a nice 15-minute video describing exactly this (controller communication via services): video
One of the original author's of Angular, Misko Hevery, discusses this recommendation (of using services in this situation) in his talk entitled Angular Best Practices (skip to 28:08 for this topic, although I very highly recommended watching the whole talk).
You can use events, but they are designed just for communication between two parties that want to be decoupled. In the above video, Misko notes how they can make your app more fragile. "Most of the time injecting services and doing direct communication is preferred and more robust". (Check out the above link starting at 53:37 to hear him talk about this)
I have something like a master controller that sets some stuff in the scope, so the inner controllers can use it.
That setup work is asynchronous, so I've wrapped it in a promise, but it's not executing it's callback unless it was already resolved (I tried setting a breakpoint, and if I wait enough, it actually runs the then callback).
Here's a fiddle that reproduces my problem with a timeout rather than a network request: http://jsfiddle.net/LMv8v/1/
HTML
<div ng-app>
<div ng-controller="configController">
<div ng-controller="testC">
{{test}}
{{foo}}
</div>
</div>
</div>
Javascript
function configController ($scope, $q) {
var deferred = $q.defer();
$scope.config = deferred.promise;
setTimeout(function() {
console.log('timeout');
deferred.resolve({
'foo' : 'baz'
});
}, 1000);
};
function testC($scope) {
$scope.test = 'I am working, uh?';
$scope.config.then(function(config) {
console.log('then...');
$scope.$apply(function() {
$scope.foo = config.foo;
});
});
};
It shows the 'timeout', but not the 'then...' message.
(I know that this would be better suited for a Service, but I already have plenty of code with the nested scopes and I want to get it working before I start refactoring)
If you are using $.getJSON() (from jQuery I am guessing)
You will run into a similar issue where you are resolving something outside of the Angular world, try the following.
$.getJSON('ajax/test.json', function(data) {
$scope.$apply(function(){
deferred.resolve({
'foo' : 'baz'
});
});
});
Example on jsfiddle with jQuery ajax