0

I have an application reading some values from an external device. I'm using angularjs as my javascript framework. I'm using angular-ui for my routing.

I'm using the resolve to pass the web socket it to the controller, since it could be used in multiple screens

.state('dashboard.count', {
    parent: 'dashboard',
    url: "/lane/:laneID",
    templateUrl: '/app/count/count.html',
    controller: 'CountCtrl as vm',
    resolve: {
        websocket: ['webSocketFactory', function (webSocketFactory) {
            return webSocketFactory.websocket;
        }]
    }
});

The websocket is retrieved by a factory:

function webSocketFactory($log, wsUri) {
    var factory = {
        sendMessage: sendMessage,
        websocket: new WebSocket('myurl')
    }

    return factory;
    }
}

In my controller I have a simple calls to the websocket API

websocket.onopen = function () {
    $log.debug('open');
    webSocketFactory.sendMessage('send my message through the factory');
    vm.connecting = false;
}

Probably 70% of the time this works just fine vm.connecting gets set to false and I can use the web-socket. However, it's unpredictable.

I'm not real happy with the accepted answer on this SO Question, The one voted higher seems to be the pattern I'm following. Does anyone have any suggestions how to make this more predictable?

Community
  • 1
  • 1
Jon Harding
  • 4,928
  • 13
  • 51
  • 96

1 Answers1

4

I suspect it's unreliable because the onopen callback would happen before your controller instance is ready. If I'm right about that, the following code should work, and also allow you to queue up a message to be sent as soon as the websocket connects.

Try adding an onopen function right after creating the new WebSocket(), and get the service to deal with all the inner workings of the socket rather than the controller.

function webSocketService($rootScope, $log, $q, wsUri) {

    var websocketConnectedDeferred, isConnected, websocket;

    var createNewWebSocket = function(url){
        websocketConnectedDeferred = $q.defer();
        isConnected = false;

        websocket = new WebSocket(url)
        websocket.onopen = onWebSocketOpen;
        websocket.onmessage = onWebSocketMessage;

        return websocket;
    };

    var onWebSocketOpen = function(){
        $log.debug('open');
        isConnected = true;
        websocketConnectedDeferred.resolve();
    };

    var onWebSocketMessage = function(incomingMessage){
        $rootScope.$broadcast('websocketMessageReceived', incomingMessage);
    };

    var sendMessage = function(message){
        websocketConnectedDeferred.promise.then(function(){
            websocket.doTheThing(message);
        });
    };

    var iswebsocketConnectedDeferred = function(){
        return isConnected;
    };

    var service = {
        sendMessage: sendMessage,
        websocket: createNewWebSocket('myUrl'),
        iswebsocketConnectedDeferred: iswebsocketConnectedDeferred
    };

    return service;
}

Controller:

$scope.$on('websocketMessageReceived', function(event, incomingMessage){
    // Do something with 'incomingMessage'
});

webSocketService.sendMessage('send my message through the factory');
HankScorpio
  • 3,612
  • 15
  • 27
  • Think it is necessary to put anything in the controller resolve? – Jon Harding Apr 09 '15 at 18:42
  • For the message to be sent, no, it will be sent by the service when the promise resolves. If you want the controller to do something after connecting is successful (display some 'Connected' message to the user or whatever), then add a method to return the `websocketConnectedPromise.promise` and respond to that. `webSocketFactory.connected().then(function(){ $scope.connectedStatus = 'Connected!!!' });` – HankScorpio Apr 09 '15 at 18:49
  • How could you place the `.onmesssage` in the controller if the factory holds the socket? – Jon Harding Apr 09 '15 at 19:07
  • Oh, I forgot to mention one other thing. If you make your webSocketFactory a service instead of a factory or resolving it as you've done in your example then you'll get the same instance in every controller and you won't need any of the state resolving code. `myModule.service('WebSocetService, function($log, $q, wsUri){ ... });` – HankScorpio Apr 09 '15 at 19:11
  • 1
    You can still assign the `onmessage` function to the websocket like this: `webSocketFactory.websocket.onmessages = function(){};` I still prefer to keep all that logic abstracted from the controller, so I'd use an event to communicate from the service. I'll update the answer with an example. – HankScorpio Apr 09 '15 at 19:12
  • sorry one more thought. my controller is keeping track of the current 'item' when I get the message it's specific to that item. Can the factory trigger something on the controller? – Jon Harding Apr 09 '15 at 19:37
  • Actually I see you using the broadcast which is what seems to be doing that – Jon Harding Apr 09 '15 at 19:38
  • When I try to send a message I get undefined when it references the `websocketConnetedPromise`. I can see it defines a promise, when it tries to advance into it it fails. It seems to work fine if I take the `websocketConnectedPromise.then(function(){` wrapper – Jon Harding Apr 09 '15 at 20:15
  • I confused myself with my own variable naming. It should have been `websocketConnectedPromise.promise.then(function(){` I've renamed it in the example to `websocketConnectedDeferred`. – HankScorpio Apr 09 '15 at 20:43