Sharing Top Content from the Angular-sphere.

[SOLVED] Node.js + AngularJS + socket.io: connect state and manually disconnect- Angular.js Recipes

  • 😉

    When models change and events fire from within the Angular framework, Angular can do dirty tracking as necessary and update any necessary views.

  • When you want to interact with code outside of Angular, you have to wrap the necessary function calls in the method of a scope, so that Angular knows something is happening.
  • It’s telling Angular, “take this code that normally wouldn’t trigger Angular view updates, and treat it like it should.”
  • For example, the following would throw an error:

    Based on your stack trace, it looks like some call to (which already uses ) triggered a call to (which also uses ).

  • We can easily build a function that takes a scope and a function to run, and then runs the function with only if one isn’t already in progress:

    Now we can replace calls to

    For example, to modify the code you have above,

[Update]

$$phase is an internal, private variable to Angular, and thus you should not really depend on it for things like this. Igor describes, in another answer, some suggestions for handling this which should be used instead (I hear he knows a thing or two about Angular. 😉

When models change and events fire from within the Angular framework, Angular can do dirty tracking as necessary and update any necessary views. When you want to interact with code outside of Angular, you have to wrap the necessary function calls in the $apply method of a scope, so that Angular knows something is happening. That’s why the code reads

$rootScope.$apply(function () {
callback.apply(socket, args);
});

and so forth. It’s telling Angular, “take this code that normally wouldn’t trigger Angular view updates, and treat it like it should.”

The problem is when you call $apply when you’re already in an $apply call. For example, the following would throw an $apply already in progress error:

$rootScope.$apply(function() {
$rootScope.$apply(function() {
// some stuff
});
});

Based on your stack trace, it looks like some call to emit (which already uses $apply) triggered a call to on (which also uses $apply). To fix this problem, we need to only call $apply if an $apply is not already in progress. Thankfully, there is a property on the scope called $$phase that can tell us if a dirty check is in progress.

We can easily build a function that takes a scope and a function to run, and then runs the function with $apply only if one isn’t already in progress:

var safeApply = function(scope, fn) {
if (scope.$$phase) {
fn(); // digest already in progress, just run the function
} else {
scope.$apply(fn); // no digest in progress, run the function with $apply
}
};

Now we can replace calls to

$rootScope.$apply(function…);

to

safeApply($rootScope, function…);

For example, to modify the code you have above,

angular.module(‘app’)
.factory(‘socket’, [‘$rootScope’, function ($rootScope) {

var safeApply = function(scope, fn) {
if (scope.$$phase) {
fn(); // digest already in progress, just run the function
} else {
scope.$apply(fn); // no digest in progress, run with $apply
}
};

var socket = io.connect();

return {
on: function (eventName, callback) {
socket.on(eventName, function () {
var args = arguments;
safeApply($rootScope, function () {
callback.apply(socket, args);
});
});
},
emit: function (eventName, data, callback) {
socket.emit(eventName, data, function () {
var args = arguments;
safeApply($rootScope, function () {
if (callback) {
callback.apply(socket, args);
}
});
})
},
disconnect: function () {
socket.disconnect();
},
socket: socket
};

}]);


@angular_recipe: Node.js + AngularJS + socket.io: connect state and manually disconnect #Angular #AngularJS

I use nodejs with socket.io and angularjs on client. I picked up angular-socketio example from the Internet and added

method to It.

Socket service:

angular.module(‘app’) .factory(‘socket’, [‘$rootScope’, 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); } }); }) }, disconnect: function () { socket.disconnect(); }, socket: socket }; }]);

Controller:

angular.module(‘app’) .controller(‘Controller’, [‘$scope’, ‘socket’, function ($scope, socket) { socket.emit(‘register’) socket.on(‘connect’, function () { console.log(‘Socket connected’); }); socket.on(‘disconnect’, function () { console.log(‘Socket disconnected’); }); socket.on(‘register’, function (reginfo) { console.log(‘Register: %s, cname=%s’, reginfo.ok, reginfo.cname); socket.disconnect(); // <-- this line throw Error }); socket.on('last', updateSnapshot); socket.on('state', updateSnapshot); function updateSnapshot(snapshot) { ... } }]); But when I try to disconnect use this method I catch Error: Error: $apply already in progress at Error () at beginPhase (http://localhost:4000/scripts/vendor/angular.js:8182:15) at Object.$get.Scope.$apply (http://localhost:4000/scripts/vendor/angular.js:7984:11) at SocketNamespace.on (http://localhost:4000/scripts/services/socket.js:10:32) at SocketNamespace.EventEmitter.emit [as $emit] (http://localhost:4000/socket.io/socket.io.js:633:15) at Socket.publish (http://localhost:4000/socket.io/socket.io.js:1593:19) at Socket.onDisconnect (http://localhost:4000/socket.io/socket.io.js:1970:14) at Socket.disconnect (http://localhost:4000/socket.io/socket.io.js:1836:12) at SocketNamespace. (http://localhost:4000/scripts/controllers/controller.js:38:34) at on (http://localhost:4000/scripts/services/socket.js:11:34)

And I don’t understand where to dig…

[Update]

$$phase

is an internal, private variable to Angular, and thus you should not really depend on it for things like this. Igor describes, in another answer, some suggestions for handling this which should be used instead (I hear he knows a thing or two about Angular. 😉

When models change and events fire from within the Angular framework, Angular can do dirty tracking as necessary and update any necessary views. When you want to interact with code outside of Angular, you have to wrap the necessary function calls in the

$apply

method of a scope, so that Angular knows something is happening. That’s why the code reads

$rootScope.$apply(function () { callback.apply(socket, args); });

and so forth. It’s telling Angular, “take this code that normally wouldn’t trigger Angular view updates, and treat it like it should.”

$apply

when you’re already in an

$apply

call. For example, the following would throw an

error:

$rootScope.$apply(function() { $rootScope.$apply(function() { // some stuff }); });

Based on your stack trace, it looks like some call to

emit

$apply

on

(which also uses

$apply

). To fix this problem, we need to only call

$apply

if an

$apply

is not already in progress. Thankfully, there is a property on the scope called

that can tell us if a dirty check is in progress.

We can easily build a function that takes a scope and a function to run, and then runs the function with

$apply

only if one isn’t already in progress:

var safeApply = function(scope, fn) { if (scope.$$phase) { fn(); // digest already in progress, just run the function } else { scope.$apply(fn); // no digest in progress, run the function with $apply } };

Now we can replace calls to

$rootScope.$apply(function…);

to

safeApply($rootScope, function…);

For example, to modify the code you have above,

angular.module(‘app’) .factory(‘socket’, [‘$rootScope’, function ($rootScope) { var safeApply = function(scope, fn) { if (scope.$$phase) { fn(); // digest already in progress, just run the function } else { scope.$apply(fn); // no digest in progress, run with $apply } }; var socket = io.connect(); return { on: function (eventName, callback) { socket.on(eventName, function () { var args = arguments; safeApply($rootScope, function () { callback.apply(socket, args); }); }); }, emit: function (eventName, data, callback) { socket.emit(eventName, data, function () { var args = arguments; safeApply($rootScope, function () { if (callback) { callback.apply(socket, args); } }); }) }, disconnect: function () { socket.disconnect(); }, socket: socket }; }]);

method which is designed to open the boundary into the angular context. But since you are already in the angular context, angular complains with the error you mentioned.

angular.module(‘app’) .factory(‘socket’, [‘$rootScope’, function ($rootScope, $timeout) { 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); } }); }) }, disconnect: function () { $timeout(socket.disconnect, 0, false); }, socket: socket }; }]);

or

angular.module(‘app’) .factory(‘socket’, [‘$rootScope’, function ($rootScope) { var socket = io.connect(), disconnecting = false; return { on: function (eventName, callback) { socket.on(eventName, function () { var args = arguments; if (!disconnecting) { $rootScope.$apply(function () { callback.apply(socket, args); }); } else { 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); } }); }) }, disconnect: function () { disconnecting = true; socket.disconnect(); }, socket: socket }; }]);

Angular.js Recipes are structured in a Cookbook format featuring recipes that contain problem statements and solutions. A detailed explanation follows each problem statement of the recipe. This is usually contained within the solution; however, an optional discussion section can often contain other useful information helping to demonstrate how the solution works.

Angular.js is a JavaScript-based open-source front-end web application framework mainly maintained by Google and by a community of individuals and corporations to address many of the challenges encountered in developing single-page applications. The JavaScript components complement Apache Cordova, the framework used for developing cross-platform mobile apps. It aims to simplify both the development and the testing of such applications by providing a framework for client-side model–view–controller (MVC) and model–view–viewmodel (MVVM) architectures, along with components commonly used in rich Internet applications.

Lost? Begin by working through Angular’s Getting Started Guide to get yourself up-and-running.

[SOLVED] Node.js + AngularJS + socket.io: connect state and manually disconnect- Angular.js Recipes

Comments are closed, but trackbacks and pingbacks are open.