2

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..

3 Answers 3

3

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);
          }
        });
      })
    }
  };
});
Sign up to request clarification or add additional context in comments.

2 Comments

My bad, missed it. Good stuff :) It's the best solution frankly speaking, wrapping socket.io as angular service makes it waaaaay easier
@maurycy added even more visible credits to the author and a version with your advice of using $applyAsync
3

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.

1 Comment

from angular 1.3 use $scope.$applyAsync which is better for this purpose
-1

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

3 Comments

Don't put $scope.$digest() in your code, if application grows and the $digest will be fired more often it will throw JS error $digest in progres use either $scope.$apply or in newer version of angularjs $scope.$applyAsync
That's why before angular 1.3 people tricket it with $timeout without specifying the time, from angular 1.3 there is the applyAsync designed for that
OK. I didn't know applyAsync.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.