Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is using if(!$scope.$$phase) $scope.$apply() an anti-pattern?

Sometimes I need to use $scope.$apply in my code and sometimes it throws a "digest already in progress" error. So I started to find a way around this and found this question: AngularJS : Prevent error $digest already in progress when calling $scope.$apply(). However in the comments (and on the angular wiki) you can read:

Don't do if (!$scope.$$phase) $scope.$apply(), it means your $scope.$apply() isn't high enough in the call stack.

So now i have two questions:

  1. Why exactly is this an anti-pattern?
  2. How can i safely use $scope.$apply?

Another "solution" to prevent "digest already in progress" error seems to be using $timeout:

$timeout(function() {   //... }); 

Is that the way to go? Is it safer? So here is the real question: How I can entirely eliminate the possibility of a "digest already in progress" error?

PS: I am only using $scope.$apply in non-angularjs callbacks that are not synchronous. (as far as I know those are situations where you must use $scope.$apply if you want your changes to be applied)

like image 892
Dominik Goltermann Avatar asked Mar 12 '14 09:03

Dominik Goltermann


People also ask

How to use scope apply in AngularJS?

In AngularJS, $apply() function is used to evaluate expressions outside of the AngularJS context (browser DOM Events, XHR). Moreover, $apply has $digest under its hood, which is ultimately called whenever $apply() is called to update the data bindings.

What is the use of dollar scope?

You can create properties to a $scope object inside a controller function and assign a value or function to it. The $scope is glue between a controller and view (HTML). It transfers data from the controller to view and vice-versa.

What is $rootScope and how does it relate to $scope?

"$rootScope” is a parent object of all “$scope” angular objects created in a web page. $scope is created with ng-controller while $rootscope is created with ng-app . Show activity on this post. The main difference is the availability of the property assigned with the object.


2 Answers

After some more digging i was able to solve the question whether it's always safe to use $scope.$apply. The short answer is yes.

Long answer:

Due to how your browser executes Javascript, it is not possible that two digest calls collide by chance.

The JavaScript code we write doesn’t all run in one go, instead it executes in turns. Each of these turns runs uninterupted from start to finish, and when a turn is running, nothing else happens in our browser. (from http://jimhoskins.com/2012/12/17/angularjs-and-apply.html)

Hence the error "digest already in progress" can only occur in one situation: When an $apply is issued inside another $apply, e.g.:

$scope.apply(function() {   // some code...   $scope.apply(function() { ... }); }); 

This situation can not arise if we use $scope.apply in a pure non-angularjs callback, like for example the callback of setTimeout. So the following code is 100% bulletproof and there is no need to do a if (!$scope.$$phase) $scope.$apply()

setTimeout(function () {     $scope.$apply(function () {         $scope.message = "Timeout called!";     }); }, 2000); 

even this one is safe:

$scope.$apply(function () {     setTimeout(function () {         $scope.$apply(function () {             $scope.message = "Timeout called!";         });     }, 2000); }); 

What is NOT safe (because $timeout - like all angularjs helpers - already calls $scope.$apply for you):

$timeout(function () {     $scope.$apply(function () {         $scope.message = "Timeout called!";     }); }, 2000); 

This also explains why the usage of if (!$scope.$$phase) $scope.$apply() is an anti-pattern. You simply don't need it if you use $scope.$apply in the correct way: In a pure js callback like setTimeout for example.

Read http://jimhoskins.com/2012/12/17/angularjs-and-apply.html for the more detailed explanation.

like image 72
Dominik Goltermann Avatar answered Sep 21 '22 22:09

Dominik Goltermann


It is most definitely an anti-pattern now. I've seen a digest blow up even if you check for the $$phase. You're just not supposed to access the internal API denoted by $$ prefixes.

You should use

 $scope.$evalAsync(); 

as this is the preferred method in Angular ^1.4 and is specifically exposed as an API for the application layer.

like image 22
FlavorScape Avatar answered Sep 21 '22 22:09

FlavorScape