Clint Berry

Full-stack Web Developer

Wannabe Entrepreneur

AngularJS WebSocket Service Example

Posted on 23 Apr 2013 in Angular | 28 comments

angular_logofull

At my curent company we are using Angular.js for a new desktop application (yes, a desktop application, but I won’t get into that). Our app gets its data and events from a web service via a WebSocket connection. Angular comes bundled with some great tools to connect to REST servers, but it doesn’t come with anything to help you with real-time data (and it probably shouldn’t).

Here is an example of an Angular service (factory) that uses WebSockets to get data:

angular.module('MyApp').factory('MyService', ['$q', '$rootScope', function($q, $rootScope) {
    // We return this object to anything injecting our service
    var Service = {};
    // Keep all pending requests here until they get responses
    var callbacks = {};
    // Create a unique callback ID to map requests to responses
    var currentCallbackId = 0;
    // Create our websocket object with the address to the websocket
    var ws = new WebSocket("ws://localhost:8000/socket/");
    
    ws.onopen = function(){  
        console.log("Socket has been opened!");  
    };
    
    ws.onmessage = function(message) {
        listener(JSON.parse(message.data));
    };

    function sendRequest(request) {
      var defer = $q.defer();
      var callbackId = getCallbackId();
      callbacks[callbackId] = {
        time: new Date(),
        cb:defer
      };
      request.callback_id = callbackId;
      console.log('Sending request', request);
      ws.send(JSON.stringify(request));
      return defer.promise;
    }

    function listener(data) {
      var messageObj = data;
      console.log("Received data from websocket: ", messageObj);
      // If an object exists with callback_id in our callbacks object, resolve it
      if(callbacks.hasOwnProperty(messageObj.callback_id)) {
        console.log(callbacks[messageObj.callback_id]);
        $rootScope.$apply(callbacks[messageObj.callback_id].cb.resolve(messageObj.data));
        delete callbacks[messageObj.callbackID];
      }
    }
    // This creates a new callback ID for a request
    function getCallbackId() {
      currentCallbackId += 1;
      if(currentCallbackId > 10000) {
        currentCallbackId = 0;
      }
      return currentCallbackId;
    }

    // Define a "getter" for getting customer data
    Service.getCustomers = function() {
      var request = {
        type: "get_customers"
      }
      // Storing in a variable for clarity on what sendRequest returns
      var promise = sendRequest(request); 
      return promise;
    }

    return Service;
}])
 

The Details

To explain this code in detail I will walk you through a usage scenario and step through each function and talk about what it does. Assume we have an angular controller called “customerList”. We need to access customer data in our new controller and our customer data comes from a websocket service somewhere in Canada. So you inject your new websocket service into the scope of your controller and you are able to call getCustomers(). Quick and dirty example for illustration purposes:

angular.module('MyApp')
  .controller('customerList', ['MyService', function(MyService){
    $scope.customers = MyService.getCustomers();
  }]);
 

So the getCustomers function is called and we see that the getCustomers function creates a request object literal and passes that to the sendRequest() function:

    // Define a "getter" for getting customer data
    Service.getCustomers = function() {
      var request = {
        type: "get_customers"
      }
      // Storing in a variable for clarity on what sendRequest returns
      var promise = sendRequest(request); 
      return promise;
    }
 

You can see I am storing the response from sendRequest() in a variable called promise. I then return that promise. Let’s look at what sendRequest() actually does:

    function sendRequest(request) {
      var defer = $q.defer();
      var callbackId = getCallbackId();
      callbacks[callbackId] = {
        time: new Date(),
        cb:defer
      };
      request.callback_id = callbackId;
      console.log('Sending request', request);
      ws.send(JSON.stringify(request));
      return defer.promise;
    }
 

The sendRequest function first creates a defer object from the Q library that is bundled with Angular. (For more information on deferred objects and promises in angular I highly recommend the egghead.io video on promises) After that it creates a new callbackId variable and then adds an object literal to the callbacks object using the new callbackId as the index.

So why have a callback ID and a callbacks object?

The callbacks variable is where I will store all requests that haven’t received a response yet. Because services implemented on the websocket side can be asynchronous, you could potentially send several requests to the websocket and the websocket could return responses in a different order than it received requests. This is where callback Ids come into play. Usually websocket servers will have a way for you to map responses from the websocket server to requests that you sent to it. Sending a user-generated callback_id to the websocket is one way to do this. In my case, I start at 0 and work my way up to 10000 then start over. You can see this in my getCallback() function:

    // This creates a new callback ID for a request
    function getCallbackId() {
      currentCallbackId += 1;
      if(currentCallbackId > 10000) {
        currentCallbackId = 0;
      }
      return currentCallbackId;
    }
 

Now back to sendRequest. After the callbackId is generated, and the deferred is stored in the callbacks variable, we add the new callbackId to the request message:

    request.callback_id = callbackId;
 

Then we send the request object to the websocket and return a promise:

    ws.send(JSON.stringify(request));
    return defer.promise;
 

Now out in Canada somewhere, our websocket server processes the request and sends back a list of customers to us through the websocket. When data comes in from the websocket we call the listener function:

    ws.onmessage = function(message) {
        listener(message);
    };
 

The listener looks at the message coming in and sees that it looks something like this:

{
  "result": true,
  "callback_id": 1,
  "data": [
    {
      first_name: Danny,
      last_name: Ocean
    },
    {
      first_name: Rusty,
      last_name: Ryan
    }
  ]
}
 

The listener() function sees the callback_id property and looks in our callbacks variable to see if we have a pending request waiting to be resolved. If there is one, it resolves the deferred object and deletes the callback object from the callbacks object-literal/dictionary/named-array:

    if(callbacks.hasOwnProperty(messageObj.callback_id)) {
      console.log(callbacks[messageObj.callback_id]);
      $rootScope.$apply(callbacks[messageObj.callback_id].cb.resolve(messageObj.data));
      delete callbacks[messageObj.callbackID];
    }

And then, lo and behold, our scope variable, $scope.customers, is populated with our new customer list! And now you have a functioning websocket service. :-)

I know this all can seem like a lot if you are new to angular or haven’t heard of promises before. Feel free to ask any questions in the comments or email me on my contact form if you need help. I am usually pretty good about getting back to you.

I am a full-stack web developer that is passionate about start-ups.

28 comments

  1. Louis / April 24th, 2013 16:06

    Have you considered using socket.io? Check this angular seed out.
    https://github.com/btford/angular-socket-io-seed

  2. Clint / April 24th, 2013 18:29

    @Louis, Totally! We used socket.io for a while, but recently switched to sockjs since it has server libraries for python tornado, twisted, and a bunch of other server-side libraries. In this case, however, we were connecting to a piece of hardware that didn’t support socket.io. Had to be raw websockets :-)

  3. Paul Liu / April 25th, 2013 7:40

    Can you tell me which Websocket server that you are running?
    If you are using node.js, are you using Sockjs or faye-websocket-node?

  4. Josh / April 28th, 2013 0:38

    Thanks for this excellent write-up, very useful! =)

  5. Clint / April 28th, 2013 1:08

    @Paul, you could use this client-side example with any websocket server. Sockjs does support a raw websocket connection but is typically used for its wrapper around websockets which requires a javascript library. Faye-websocket-node is the lower level implementation and would work just fine as well.

  6. sik / June 11th, 2013 9:13

    Thank you for really helpful example and kind explanation!
    But I’m not sure I understand that totally. I understand that the server should return the callbackId(which is from client request) to client, is this right?
    And one more question!
    If I manipulate “sendRequest()” function for inserting callback function for each request, then would it be possible that I can specify callback function for each request in the controller function(“customerList” in example)?
    I want use the WebSocket send function like jQuery.ajax function. It can specify callback for each request.
    I’m really not good at Engilsh so, it may cause you headache to read. But hopfully waiting for your answer.
    And again, really thank you for example!

  7. Clint Berry / June 19th, 2013 5:28

    @sik – Yes, the server needs to return the callback ID so the client knows what request it is for. Also, you can totally use callbacks if you want. Here is an untested Gist where I add callbacks as an option: https://gist.github.com/clintberry/5811856

  8. Tomer Chachamu / June 22nd, 2013 18:40

    Your websocket onmessage should be function(message) { listener(JSON.parse(message.data)); }

  9. Clint Berry / June 23rd, 2013 22:27

    @Tomer – Thanks :-)

  10. Zach / June 25th, 2013 14:04

    Have you implemented any server push capabilities with this setup at all? For instance, after the initial request, getting automatic updates from the server if a customer is added. Might not be in the scope of your application, but I’d be curious how you structured it if so.

  11. Daniel Ochoa / June 25th, 2013 22:05

    Why the “MyService” service not have the parameter $rootScope?

  12. Clint Berry / June 26th, 2013 2:42

    @Zack – Yes, we handle messages from the server with this method all the time. What we do is change the listen function to look for a callback_id, and if it doesn’t exist it assumes it is an event and publishes it to the app. Let me know if you need more detail and I make a gist.

  13. Clint Berry / June 26th, 2013 2:45

    @Daniel – whoops. sorry about that. Will add it now.

  14. Daniel Ochoa / June 26th, 2013 16:17

    Hi Clint, I have the next problem when execute the code:

    An attempt was made to use an object that is not, or is no longer, usable. in line ws.send(JSON.stringify(request));

    The problem that I view, is that the object ws is not ready to use. If I put the code ws.send(JSON.stringify(request)) in function ws.onopen the code execute without problem . Why can this happen?

    Thanks

  15. wil / June 30th, 2013 13:53

    We have a desktop requirement for which we’d like to use AngularJS too. What platform/os/runtime are you using to implement your desktop app? We are thinking about looking at Chrome and Windows 8, but have not begun to look into the question in depth.)

  16. Clint Berry / July 1st, 2013 22:22

    @wil, I have a blog post on it! :-) http://clintberry.com/2013/html5-desktop-apps-with-brackets-shell/

  17. James / July 6th, 2013 19:29

    Hi Clint,

    Great post! Forgive me, as I am new to the Angular framework, but how would you integrate this into the resolve property of the $routeProvider, so that the route only fired a $routeChangeSuccess event when the data had been brought in from the socket?

    E.g. in your example – I would want to remove my loading animation on an imaginary #/customers route only after the customers data had been returned from the socket.

    Thanks a lot in advance,

    James

  18. Clint Berry / July 14th, 2013 19:02

    @James – I don’t know if the $routeChangeSuccess event is what you want to do a loading animation. One of the cool things about angular views is that you can pass it promise variables and it will automatically render them when the promise resolves. If you wanted to do a loading screen, you would instead use the .then() function on your promise to remove the loading icon and replace it with the data.

  19. Christian / July 31st, 2013 16:07

    Thanks for the tutorial. Its the only one I could find on the web!

    How do I call the factory object to open a connection on page load? Any idea?

    Many thanks

  20. Joseph Hughes / September 3rd, 2013 1:46

    Thanks for the great turtorial!
    I am having trouble because Angular JS is trying to send a message before the connection completely opened. So I am getting an Invalid State Error. Reading the Websocket spec this should happen is the Websocket is in the “CONNECTING” state. What would you recomend as the best way to avoid this situation and solve the problem?

    Thanks,

    Joe

  21. Clint Berry / September 3rd, 2013 6:17

    @Joseph – That is definitely an issue. I created a gist for you showing you how we resolve it in a new open source project we are working on for real-time phone system monitoring: https://gist.github.com/clintberry/6420200 It isn’t quite done being implemented, but look at the comment under it for more info.

  22. svbito / September 4th, 2013 13:33

    That article clarified a lot for me! Thanks!

    You said:

    What we do is change the listen function to look for a callback_id, and if it doesn’t exist it assumes it is an event and publishes it to the app. Let me know if you need more detail and I make a gist”

    A gist would be great!

    Thanks

  23. Paul B / September 7th, 2013 13:33

    Same as Daniel Ochoa…

    An attempt was made to use an object that is not, or is no longer, usable. in line ws.send(JSON.stringify(request));

    Any idea why this is?

  24. Clint Berry / September 13th, 2013 4:49

    @Paul & @Daniel – If you attempt to send a message over the websocket before it connects completely it will give you that error. You can set a timeout, but that is lazy and not a good solution. The best solution is to store requests until the connection is complete and then send the requests. Hope that helps.

  25. Manuel / October 21st, 2013 7:35

    Hi Clint,
    can you please explain mor detailed how you “publish” your event to the app? (“if it doesn’t exist it assumes it is an event and publishes it to the app”)

  26. Wojciech / November 21st, 2013 17:48

    Hi,
    why are we passing the callback as an agrument to scope.$apply? Angular documentation claims that expression passed to $apply will be evaluated before the scope gets updated – why are we evaluating the JSON dictionary there?

  27. Lionel / December 11th, 2013 8:37

    I can’t get the above code to return the correct result. It seems to be the promise itself rather than the value. Probably the version of Angular is different?
    Anyway, I got the result from the WebSocket in this way.

    $scope.someVariable = null; // Initialize
    SomeService.getValueForVariable().then(function (result) {
    $scope.someVariable = result; // Set the result.
    })

    I’m not an Angular expert, so if you guys have better advice, I’m at all ears. :)

  28. Jeffrey Jose / January 10th, 2014 18:46

    To error that says –
    An attempt was made to use an object that is not, or is no longer, usable. in line ws.send(JSON.stringify(request));

    Check for ws.readyState!=0/code> to ensure WebSocket communcation has been established to the backend.


    if (ws.readyState != 0){
    ws.send(JSON.stringify(request));
    }
    else {
    console.log('Socket not ready.')
    }

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>