1

I need a global application wise web-socket, which can be used by all of my controller in app.

The following code describes the my use-case where I am creating a factory to share a singleton websocket object. The interface provided are allows the controllers to send the data over socket and attach listener for incoming messages.

The problem with the following code is that I am able to send the data on socket as expected, but not able to receive data when it comes over the socket.

I have read several angular digest related solutions, but they are not working for me.

angular
  .module('myApp', [])
  .factory('socket', function() {
    var socketStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
    var socket = new WebSocket("ws://localhost:7070/ws");
    socket.onopen = function(event) {
      socket.send("hello");
    };

    socket.addEventListener('message', function(event) {
      // I am able to see this so, it is confirmed that server is sending the data
      console.log(event);
    });

    return {
      emit: function (data) {
        socket.send(JSON.stringify(data));
      },
      on: function(callback) {
        socket.addEventListener('message', callback);
      },
      getStatus: function() {
        var status = socket.readyState;
        return socketStates[status]
      }
    };
  })
  .controller('MyController', function(socket){
    var myctl = this;

    //This works
    socket.emit({type : "register", name: producer});

    //This doesn't work
    socket.on(function(event) {
      console.log(event);
    });
  });

$rootScope.$apply(socket.addEventListener('message', callback));

instead of

socket.addEventListener('message', callback);

throws the following error

vendor.js:5 Error: [$rootScope:inprog] $digest already in progress(…)(anonymous function)

Jayendra Parmar
  • 702
  • 12
  • 30

2 Answers2

2

The digest process is already in progress you have to check the $$phase and wrap your code with if statement:

if (!$scope.$$phase) {
  $rootScope.$apply(socket.addEventListener('message', callback));
} else {
  socket.addEventListener('message', callback);  
}

For more check: AngularJS : Prevent error $digest already in progress when calling $scope.$apply()

Community
  • 1
  • 1
Oskar
  • 2,548
  • 1
  • 20
  • 22
  • can we have `$scope` inside factory ? – Jayendra Parmar Apr 18 '17 at 14:18
  • In theory you can, but I wouldn't recommend that, if you use `$rootScope` in your factory consider refactoring your code. `$$phase` probably works with `$rootScope` as well – Oskar Apr 18 '17 at 14:25
  • It's working now, and don't understand how, becuase my code is going in to still else part. And more thing why `(!$scope.$$phase)` is considered as anti-pattern ? – Jayendra Parmar Apr 18 '17 at 14:44
  • @bruce_wayne I think it's prone to error, because it can create race conditions and fire many unnecessary `$apply` / `$digest` phases one after another – Oskar Apr 18 '17 at 16:20
  • what's the better way to do it ? – Jayendra Parmar Apr 18 '17 at 16:22
  • @bruce_wayne First of all extract `$rootScope` / `$scope` from your factories/services, expose the needed functionality as API in your factories/services and call it from your controllers. Check the information flow in your app, if you're not sure if the piece of code happens in `$digest` phase or is a direct cause of `$digest` then you need to refactor, sooner or later it will blow into your face with an error hard to find. Basically, if a `$digest` is already triggered, it will check if you changed your values after is completed. If something has changed, `$digest` is fired again. – Oskar Apr 18 '17 at 17:07
  • I can't do that that's the whole point of using websockets, I don't know when message will appear inside the controller from the server. So there is no way beforehand to call the $digest loop, unless I do it in factory. – Jayendra Parmar Apr 19 '17 at 05:56
  • @bruce_wayne Yes, but you can for instance use events in your application rather than calling `$rootScope.apply` and let the other components to decide when to call `apply` and what to do with the data – Oskar Apr 19 '17 at 10:36
0

Your socket.on() event is running digest cycle which is conflicting with current digest cycle.

you can use setTimeut() to solve this problem

setTimeout(function(){ 
       socket.on(function(event) {
          console.log(event);
      }); 
});
hasan
  • 3,484
  • 1
  • 16
  • 23