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:
myApp.controller('myController', function($scope, myFactory) {
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() {
if (!$scope.username || !$scope.password) {
}
else {
if ($scope.credentialsSaved) {
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) {
}, function(error){
});
}
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.
myApp.factory('myFactory', function ($scope, $websocket) {
var factory = {};
factory.dataStream;
factory.connect = function (username, password,OnMessageCallback, OnErrorCallback) {
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:
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:
$scope.messageReceived = false;
function getConnection() {
myFactory.connect($scope.username, $scope.password, function (data) {
$scope.$apply(function () {
$scope.messageReceived = true;
$scope.message = message;
});
}, function(error){
});
}
And at the nested controller:
$scope.$watch('messageReceived', function (newVal) {
if (newVal) {
}
});
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