Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS - Best method to trigger digest from outside of Angular

I'm not quite sure the title is the best description but I was wondering what the best method would be if I had some event that occurs outside of the Angular digest loop, to trigger a digest loop? In an application I'm working on, I'm using Socket.io to pass data back and forth from other applications within the network. There are some events from Socket.io that may be used to update models within the current Controllers $scope. These changes aren't reflected in the UI until a digest loop occurs though. It's prefered that I not have to call $scope.$apply() to prevent running into issues with Angular already being in a loop.

I know I could use $timeout() and just drop my function within there and set the timeout to be 0 seconds. That's what I'm currently using and it works fine but I'm wondering if this is the best approach?

Example JS

var app = angular.module('MyApp',[]);
var socket = io.connect('http://localhost:3000');

app.controller('MyAppController',['$scope',function($scope) {
    $scope.connections = 0;
    socket.on('connect',function(sock) {
        sock.on('event',function() {
            $scope.connections++;
        };
    };
}]);

Example HTML

<!DOCTYPE html>
<html lang="en" ng-app="MyApp">
<head></head>
<body ng-controller="MyAppController">
    <p>Connections: {{connections}}</p>
    <script src="angular.min.js"></script>
</body>
</html>

Obviously this is a very stripped down, as basic as possible example. It's not the ACTUAL code in use, but represents the same issue. When the event is raised from socket.io, the $scope.connections variable is incremented but the UI doesn't reflect the change until something else triggers a digest loop, $scope.$apply() is called, or I use $timeout().

Once again, I have code that works, just wondering what the best approach to this is.

Thanks

like image 329
dferg Avatar asked Dec 15 '22 16:12

dferg


1 Answers

Whenever you know you're not in a digest loop, it's always safe (and the best practice) to call $scope.$apply() or $scope.digest(). How do you know? Any async call that is not done through an Angular API. This is how angular handles these situations internally (look at the code for the ngEvent directives).

Use $timeout only when you aren't sure whether the code you are running will be "inside" angular or not. This should be very, very rare. I've only needed this once and I'm still not very happy about it...

var app = angular.module('MyApp',[]);
var socket = io.connect('http://localhost:3000');

app.controller('MyAppController',['$scope',function($scope) {
    $scope.connections = 0;
    socket.on('connect',function(sock) {
        sock.on('event',function() {
            //wraps your code in a try catch that is handled by angular's error service.  Calls $rootScope.$digest()           
            $scope.$apply(function() {
                $scope.connections++;
            });

            //OR - errors in your code will not be handled by angular
            //also calls $rootScope.$digest()
            $scope.connections++;
            $scope.$apply();

            //OR - errors in your code will not be handled by angular
            //Only processes watchers for $scope and it's children
            //Fastest, but may have unintended consequences.
            $scope.connections++;
            $scope.$digest();
        };


    };
}]);

I prefer the first option, because it gets your code "inside angular". However, all three will work.

like image 88
Joe Enzminger Avatar answered Feb 12 '23 11:02

Joe Enzminger