My AngularJS CRUD application processes it's information over a WebSocket Server. (This was mainly so that updates from one user would get automatically pushed to all users without the need for massive HTTP polling)
I realized early on that I would have to set up my services differently than I normally do with HTTP services. Normally, for each Model that I am working with, I give them their own service to populate that particular Model. However, this is not feasible with a Websocket Connection, because I don't want a separate connection for each service. Therefore, there are a couple of solutions.
1) set up a single service that establishes a connection, then share that connection with other services that will use that service to make their specific queries
2) make a single, type-agnostic service that will be used by all controllers that need access to the connection and data.
Option 2 seemed much easier to manage and would be reusable across applications, so I started on that. That was when I realized that this was actually an opportunity. Rather than explicitly creating models for each type of data that the Client could receive, I could create a master data
object, and dynamically create child objects of myService.data
as needed when data flows in from requests. Thus, if I ever need to update my Model, I just update the Model at the server level, and the client already knows how to receive it; it will just need a Controller that knows how to use it.
However, this opportunity brings a drawback. Apparently, because myService.Data
is an empty, childless object at creation, any Scope that wants to reference its future children have to simple reference the object itself.
For example, $scope.user = myService.data.user
throws an error, because that object doesn't exist at declaration. it would appear that my only option is for each controller to simply have $scope.data = myservice.data
, and the view for each controller will simply have to use
< ng-model='data'>
, with the declarations being something like {{data.user.username}}
. I have tested it, and this does work.
My question is this; Is there any way I can get the best of both worlds? Can I have my service update it's data model dynamically, yet still have my controllers access only the part that they need? I? I was feeling quite clever until I realized that all of my Controllers were going to have access to the entire data model... But I honestly can't decide if that is even a huge problem.
Here is my Service:
app.factory('WebSocketService', ['$rootScope', function ($rootScope) {
var factory = {
socket: null,
data: {},
startConnection: function () {
//initialize Websocket
socket = new WebSocket('ws://localhost:2012/')
socket.onopen = function () {
//todo: Does anything need to happen OnOpen?
}
socket.onclose = function () {
//todo: Does anything need to happen OnClose?
}
socket.onmessage = function (event) {
var packet = JSON.parse(event.data);
////Model of Packet:
////packet.Data: A serialised Object that contains the needed data
////packet.Operation: What to do with the Data
////packet.Model: which child object of Factory.data to use
////packet.Property: used by Update and Delete to find a specific object with a property who's name matches this string, and who's value matches Packet.data
//Deserialize Data
packet.Data = JSON.parse(packet.Data);
//"Refresh" is used to completely reload the array
// of objects being stored in factory.data[packet.Model]
// Used for GetAll commands and manual user refreshes
if (packet.Operation == "Refresh") {
factory.data[packet.Model] = packet.Data
}
//Push is used to Add an object to an existing array of objects.
//The server will send this after somebody sends a successful POST command to the WebSocket Server
if (packet.Operation == "Push") {
factory.data[packet.Model].push(packet.Data)
}
if (packet.Operation == "Splice") {
for (var i = 0; i < factory.data[packet.Model].length; i++) {
for (var j = 0; j < packet.Data.length; j++){
if (factory.data[packet.Model][i][packet.Property] == packet.Data[j][packet.Property]) {
factory.data[packet.Model].splice(i, 1);
i--;
}
}
}
}
// Used to update existing objects within the Array. Packet.Data will be an array, although in most cases it will usually only have one value.
if (packet.Operation == "Update") {
for (var i = 0; i < factory.data[packet.Model].length; i++) {
for (var j = 0; j < packet.Data.length; j++) {
if (factory.data[packet.Model][i][packet.Property] == packet.Data[j][packet.Property]) {
factory.data[packet.Model][i] = packet.Data[j]
i--;
}
}
}
}
//Sent by WebSocket Server after it has properly authenticated the user, sending the user information that it has found.
if (packet.Operation == "Authentication") {
if (packet.Data == null) {
//todo: Authentication Failed. Alert User Somehow
}
else {
factory.data.user = packet.Data;
factory.data.isAuthenticated = true;
}
}
$rootScope.$digest();
}
},
stopConnection: function () {
if (socket) {
socket.close();
}
},
//sends a serialised command to the Websocket Server according to it's API.
//The DataObject must be serialised as a string before it can be placed into Packet object,which will also be serialised.
//This is because the Backend Framework is C#, which must see what Controller and Operation to use before it knows how to properly Deserialise the DataObject.
sendPacket: function (Controller, Operation, DataObject) {
if (typeof Controller == "string" && typeof Operation == "string") {
var Data = JSON.stringify(DataObject);
var Packet = { Controller: Controller, Operation: Operation, Data: Data };
var PacketString = JSON.stringify(Packet);
socket.send(PacketString);
}
}
}
return factory
}]);
Here is a Simple Controller that Accesses User Information. It is actually used in a permanent header <div>
in the Index.html, outside of the dynamic <ng-view>
. It is responsible for firing up the Websocket Connection.
App.controller("AuthenticationController", function ($scope, WebSocketService) {
init();
function init() {
WebSocketService.startConnection();
}
//this is the ONLY way that I have found to access the Service Data.
//$scope.user = WebSocketService.data.user doesn't work
//$scope.user = $scope.data.user doesn't even work
$scope.data = WebSocketService.data
});
And here is the HTML that uses that Controller
<div data-ng-controller="AuthenticationController">
<span data-ng-model="data">{{data.user.userName}}</span>
</div>