Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / Cordova

How to Use Web Sockets in an AngularJS SPA on Apache Cordova

5.00/5 (3 votes)
6 Mar 2018CPOL3 min read 8.5K  
I've been using web sockets the same way as a regular http request; I wasn't using them to their full potential...

Introduction

I have an SPA built in AngularJS in which I needed to request data from the server and I wanted to use web sockets. With web sockets, the client and the server have a duplex communication and information can be pushed to the client, so he doesn’t need to pull data on a regular basis. Additionally, once the connection is established, no extra packages roundtrip is required for handshake of certificates or credentials.

Background

You could read more about the advantages of web sockets here.

If this SPA is running on a phone using Apache Cordova, like it is my case, another important point to read about is the energy consumption related to web sockets. You could read about this here.

Using the Code

SPAs usually requests information through http request. Through a factory or directly, each controller is responsible for requesting the information it should display, building the request and waiting for the response to be processed.

So my first version of the application naturally follows this structure. It used web sockets more in the same way that an http request would work: open connection, send the request, wait for the response and close the connection. Until I’ve realized, it wasn’t using the web socket to its full potential....

So, in the second version of my application, the connection is established only once for the period of time it’s valid.

In order to build it, I do the following:

1. Build a Main Controller

Because my application runs on a phone using Apache Cordova, in a main controller, I listen to the events DeviceReady, Pause and Resume. DeviceReady and Resume open the connection and Pause closes the connection. I didn’t want the connection to persist when the application is in the background to avoid unneeded consumption.

Also, if your web socket requires any authentication, credentials should be sent when requesting the connection and not within each message. If there is any authentication, when DeviceReady, if username and password are not available, there should not be any attempt to connect. Also, whenever the credentials change, the application should disconnect and reconnect.

So, this is my main controller:

JavaScript
myApp.controller('myController', function($scope, myFactory) {

  document.addEventListener("deviceready", onDeviceReady, false);

  function onDeviceReady() {
       //do not try to connect without credentials if required
       if (!$scope.username || !$scope.password) {
            //go to set credentials
       }
       else {
           if ($scope.credentialsSaved) { //user has changed credentials, so reconnect
                if (myFactory.isConnected()) {
                    removeConnection();
                 }
                getConnection();
           }
           else {
               if (!myFactory.isConnected()) {
                    getConnection();
               }
            }          
       }
    }

  document.addEventListener("pause", onPause, false);

  function onPause() {
      if (myFactory.isConnected()) {
          removeConnection();
       }
  }

  document.addEventListener("resume", onResume, false);

  function onResume() {
      if (!myFactory.isConnected()) {
           getConnection();
      }
  }

    function getConnection() {
      myFactory.connect($scope.username, $scope.password, function (data) {
         //do what ever should be done with the message
      }, function(error){
          //do something
       });
  }


  function removeConnection() {
      myFactory.disconnect();
  }
});

2. Build a Factory

I built a factory with 4 methods: connect, isConnected, disconnect and getData. isConnect is useful to avoid errors trying to disconnect when not connected or to connect when connected.

JavaScript
myApp.factory('myFactory', function ($scope, $websocket) {

    var factory = {};

    factory.dataStream;

    factory.connect = function (username, password,OnMessageCallback, OnErrorCallback) {
        //add the username and password to the query string 
        //if username and password is required by your server
        var path = "wss://localhost:443/webSocketMethod?u="+username+"&p="+password;

        this.dataStream = $websocket(path);

        this.dataStream.onError(function (error) {
            OnErrorCallback(error));
        });

        this.dataStream.onMessage(function (message) {
            OnMessageCallback(message));
        });
    }

    factory.disconnect = function () {
        this.dataStream.close();
    }

   factory.isConnected = function () {
        return this.dataStream != undefined;
    }

    factory.getData = function (params) {
        this.dataStream.send(params);
    };

    return factory;
});

3. Sending Messages

Each controller needing data should build and send its own message:

JavaScript
function getData() {
        var parameters = {
            from: from,
              ...
            to: to
        };
        var params = JSON.stringify(parameters);
     myFactory.getData(params);
  }

4. Customize the Callbacks When Receiving Messages

The connect method in myFactory sets the callbacks when a message is received and in case of an error. If the callback behavior shall be different depending on the view, the callback could be customized using $scope.$apply() method in the main controller to update bindings and $scope.$watch in the nested controllers.

At the main controller:

JavaScript
$scope.messageReceived = false;

function getConnection() {
    myFactory.connect($scope.username, $scope.password, function (data) {
        //do what ever should be done with the message
        $scope.$apply(function () {
           $scope.messageReceived = true;
           $scope.message = message;
        });
      }, function(error){
           //do something
     });
}

And at the nested controller:

JavaScript
$scope.$watch('messageReceived', function (newVal) {
    if (newVal) {
       //do whatever you need with the $scope.message
    }
});

So, in this way, the application connected is able not only to request data from the server but also to listen to information push from the server. Dealing with the connection at code level is centralized in one single place. The main controller and the nested controllers are able to customize messages and reactions to the responses in a quite flexible way.

History

  • 7th March, 2018: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)