Updated on December 20, 2016
A better safeApply with Angular 1.5
Many of you might have run into this error at some point in time:
Error: $digest already in progress
This is typically created when someone calls $scope.$apply() in the middle of their code. Angular can be finicky about calling $apply when the $digest loop (2) is being executed. The error is essentially saying “I’m already $applying a bunch of stuff, I can’t add any more to the list right now.”
You might have searched for the issue and found a workaround solution like the one described here. This one shows a “safeApply” method that identifies the phase of the $digest loop and attempts to execute the function if it’s within the $apply or $digest phase, otherwise it applies the function using the $apply method.
$scope.safeApply = function(fn) { var phase = this.$root.$$phase; if(phase == '$apply' || phase == '$digest') { if(fn && (typeof(fn) === 'function')) { fn(); } } else { this.$apply(fn); } };
While this code performs beautifully, allowing you to call $scope.safeApply() instead of $scope.$apply() to see the changes in your layout immediately, it’s not as succinct as it could be.
Here’s a better way to accomplish the same goal.
angular.module('MyModule') .controller('MyController', function($scope, $timeout){ $scope.myVar = "Hello"; $scope.fixedApplyFunction = function(){ // Use $timeout() instead of $scope.$apply() or $scope.safeApply(). $timeout(function(){ $scope.myVar = "world"; }); }; });
Why does $timeout work the same as safeApply()? Well, $timeout is an Angular service that defers function execution and executes that function at a specified time (in simplistic terms, but the code is a little more involved). If you don’t specify a time parameter then it executes the function immediately, also executing a full digest loop thereby updating your layout with any resulting changes.
Using $timeout also enables better unit tests because you can use $timeout.flush() to immediately execute these $timeout tasks and review your test results using expects.
My recommendation is to avoid the use of $scope.$apply() as much as possible. There’s rarely a need to use it, and if you find yourself needing it then you should know exactly why, and consider other alternatives such as $timeout() first.
Recent Comments