AngularJs variable not updating

smk picture smk · Mar 29, 2014 · Viewed 22.5k times · Source

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

Answer

Peter Gerasimenko picture Peter Gerasimenko · Mar 29, 2014

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