I have a node.js server application with the following setup :
var express = require('express');
var io = require('socket.io');
var http = require('http');
var app = express();
app.use(express.static('public'));
var server = http.createServer(app);
io = io.listen(server);
io.on('connection', function(socket){
console.log('a user is connected');
});
device.on('disconnect', function() {
console.log('we got disconnected! :( ');
io.emit('deviceDisconnection');
});
Device here is a Bluetooth connected device.
And, client side, I have a AngularJS application with the following event subscription declared in the controller of the application:
var appFront = angular.module(appFront , [])
.controller('mainController', ['$scope', '$http', function($scope,$http) {
var socket = io();
socket.on('deviceDisconnection', function(){
$scope.connected = 'TRUE';
});
}]);
And sure in the view, I have the following binding:
<div class="row">
<p>{{connected}}</p>
</div>
The issue is that the change is not really "real time". I mean, the connected
value is probably changed in the scope but there is a need to do an action on the page (button clic) to make the view update with the right value. If I'm not doing anything on the page the value is not refreshed.
I probably forgot something.. But I have no clue about what..
Socket.io events are external to AngularJS so you need to warn AngularJS that a change has just happened:
var appFront = angular.module(appFront , [])
.controller('mainController', ['$scope', '$http', function($scope,$http) {
var socket = io();
socket.on('deviceDisconnection', function(){
$scope.$applyAsync(function () {
$scope.connected = 'TRUE';
});
});
}]);
Now this could be quite cumbersome, so you could create a factory to handle that for you
app.factory('socket', function ($rootScope) {
var socket = io.connect();
return {
on: function (eventName, callback) {
socket.on(eventName, function () {
var args = arguments;
$rootScope.$apply(function () {
callback.apply(socket, args);
});
});
},
emit: function (eventName, data, callback) {
socket.emit(eventName, data, function () {
var args = arguments;
$rootScope.$apply(function () {
if (callback) {
callback.apply(socket, args);
}
});
})
}
};
});
You can check more info about that code snippet in here, credits go to the author Brian Ford.
EDIT
As maurycy said it, since Angular 1.3 is better to use $applyAsync, and since the snippet above was written in 2012 it's a bit outdated, so a better version would be something like that
app.factory('socket', function ($rootScope) {
var socket = io.connect();
return {
on: function (eventName, callback) {
socket.on(eventName, function () {
var args = arguments;
$rootScope.$applyAsync(function () {
callback.apply(socket, args);
});
});
},
emit: function (eventName, data, callback) {
socket.emit(eventName, data, function () {
var args = arguments;
$rootScope.$applyAsync(function () {
if (callback) {
callback.apply(socket, args);
}
});
})
}
};
});
Angular isn't aware of changes outside of it's context. To fix that, you should use $apply
like this:
var appFront = angular.module(appFront , []).controller('mainController', ['$scope', '$http', function($scope,$http) {
var socket = io();
socket.on('deviceDisconnection', function(){
$scope.$apply(function () {
$scope.connected = 'TRUE';
});
});
}]);
$scope.$apply()
will execute a function passed as a parameter and then call $scope.$digest() to update any bindings or watchers.
Because the socket event is outside AngularJS, you need to digest the scope :
socket.on('deviceDisconnection', function(){
$scope.connected = 'TRUE';
$scope.$digest();
});
You can read this interesting article to really understand why this is needed