1

I've been working on a webapp where I have to receive messages on a websocket and make changes.

So basically, I have something like:

var socketService = angular.module('socketService');

socketService.factory('Listen', function () {
    // connect etc.
    socket.onmessage = function (msg) {
        lastMsg = msg;
        console.log(msg); // this is instant
    }

    return {
        lastMsg: function () {
            return lastMsg;
        }
    }
});

And I've got another module I'm using this service in, inside a controller

var mainMod = angular.module('mainMod', ['socketService']);
// some more stuff
mainMod.controller('MainCtrl', function(Listen) {
    $scope.$watch(Listen.lastMsg, function (newmsg, oldmsg) { // this is laggy
        // do stuff here
    });
});

The issue is this: my $watch doesn't trigger as soon as there's a message received on the socket. If I console.log all the socket messages in the service, the logs appear instantly, but $watch takes its own sweet time to trigger. Also, it's quite irregular – I don't see a pattern in the lags.

I'm thinking this has to do with Angular's ticks – and the $watch compares on every tick, but that severely affects my app's performance.

One possible workaround is to use $broadcast, but I don't want that approach.

What should I do here?

Ashesh
  • 2,978
  • 4
  • 27
  • 47

1 Answers1

3

Your lastMsg is a primitive, and plus you are listening on your $scope for lastMsg but you aren't triggering a $scope.$digest (usually through $scope.$apply, but safer through $timeout) cycle when it changes. for your $watch to trigger, you'll need:

var socketService = angular.module('socketService');

socketService.factory('Listen', function ($timeout) {
    var lastMsg;
    // connect etc.

    socket.onmessage = function (msg) {
        $timeout(function(){ // acts as a $rootScope.$apply
          lastMsg = msg;
          console.log(msg);
        });
    }

    return {
        lastMsg: function () {
            return lastMsg;
        }
    }
});

A better approach would instead $rootScope.$emit the event, that way you could receive the event as soon as it's emitted:

var socketService = angular.module('socketService');

socketService.factory('Listen', function ($rootScope) {
    // connect etc.

    socket.onmessage = function (msg) {
        $rootScope.$emit('socket', msg); 
    }

    return {
    };
});


var mainMod = angular.module('mainMod', ['socketService']);
// some more stuff
mainMod.controller('MainCtrl', function(Listen) {
    // when you inject Listen, your service singleton will be initialized
    $scope.$on('socket', function(event, msg) {
        // do stuff here
    });
});
pocesar
  • 6,860
  • 6
  • 56
  • 88
  • 2
    Use $rootScope.$emit + $rootScope.$on instead of $broadcast: http://stackoverflow.com/questions/11252780/whats-the-correct-way-to-communicate-between-controllers-in-angularjs/19498009#19498009 – tasseKATT Mar 28 '14 at 14:16
  • 1
    interesting @tasseKATT although that's a micro optimization, unless he will be issuing 30k messages per second (and it was improved in angular 1.2+) – pocesar Mar 28 '14 at 14:20
  • Great answer, thanks. I also agree with @tasseKATT, the reasons why I didn't want to $broadcast was to avoid performance setbacks. – Ashesh Mar 28 '14 at 14:22