AngularJS : Prevent error $digest already in progress when calling $scope.$apply()

前端 未结 28 2665
伪装坚强ぢ
伪装坚强ぢ 2020-11-21 22:31

I\'m finding that I need to update my page to my scope manually more and more since building an application in angular.

The only way I know of to do this is to call

相关标签:
28条回答
  • 2020-11-21 23:14

    Found this: https://coderwall.com/p/ngisma where Nathan Walker (near bottom of page) suggests a decorator in $rootScope to create func 'safeApply', code:

    yourAwesomeModule.config([
      '$provide', function($provide) {
        return $provide.decorator('$rootScope', [
          '$delegate', function($delegate) {
            $delegate.safeApply = function(fn) {
              var phase = $delegate.$$phase;
              if (phase === "$apply" || phase === "$digest") {
                if (fn && typeof fn === 'function') {
                  fn();
                }
              } else {
                $delegate.$apply(fn);
              }
            };
            return $delegate;
          }
        ]);
      }
    ]);
    
    0 讨论(0)
  • 2020-11-21 23:16

    Handy little helper method to keep this process DRY:

    function safeApply(scope, fn) {
        (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
    }
    
    0 讨论(0)
  • 2020-11-21 23:19

    I have been able to solve this problem by calling $eval instead of $apply in places where I know that the $digest function will be running.

    According to the docs, $apply basically does this:

    function $apply(expr) {
      try {
        return $eval(expr);
      } catch (e) {
        $exceptionHandler(e);
      } finally {
        $root.$digest();
      }
    }
    

    In my case, an ng-click changes a variable within a scope, and a $watch on that variable changes other variables which have to be $applied. This last step causes the error "digest already in progress".

    By replacing $apply with $eval inside the watch expression the scope variables get updated as expected.

    Therefore, it appears that if digest is going to be running anyways because of some other change within Angular, $eval'ing is all you need to do.

    0 讨论(0)
  • 2020-11-21 23:19
            let $timeoutPromise = null;
            $timeout.cancel($timeoutPromise);
            $timeoutPromise = $timeout(() => {
                $scope.$digest();
            }, 0, false);
    

    Here is good solution to avoid this error and avoid $apply

    you can combine this with debounce(0) if calling based on external event. Above is the 'debounce' we are using, and full example of code

    .factory('debounce', [
        '$timeout',
        function ($timeout) {
    
            return function (func, wait, apply) {
                // apply default is true for $timeout
                if (apply !== false) {
                    apply = true;
                }
    
                var promise;
                return function () {
                    var cntx = this,
                        args = arguments;
                    $timeout.cancel(promise);
                    promise = $timeout(function () {
                        return func.apply(cntx, args);
                    }, wait, apply);
                    return promise;
                };
            };
        }
    ])
    

    and the code itself to listen some event and call $digest only on $scope you need

            let $timeoutPromise = null;
            let $update = debounce(function () {
                $timeout.cancel($timeoutPromise);
                $timeoutPromise = $timeout(() => {
                    $scope.$digest();
                }, 0, false);
            }, 0, false);
    
            let $unwatchModelChanges = $scope.$root.$on('updatePropertiesInspector', function () {
                $update();
            });
    
    
            $scope.$on('$destroy', () => {
                $timeout.cancel($update);
                $timeout.cancel($timeoutPromise);
                $unwatchModelChanges();
            });
    
    0 讨论(0)
  • 2020-11-21 23:20

    The issue is basically coming when, we are requesting to angular to run the digest cycle even though its in process which is creating issue to angular to understanding. consequence exception in console.
    1. It does not have any sense to call scope.$apply() inside the $timeout function because internally it does the same.
    2. The code goes with vanilla JavaScript function because its native not angular angular defined i.e. setTimeout
    3. To do that you can make use of

    if(!scope.$$phase){
    scope.$evalAsync(function(){

    }); }

    0 讨论(0)
  • 2020-11-21 23:21

    I would advise you to use a custom event rather than triggering a digest cycle.

    I've come to find that broadcasting custom events and registering listeners for this events is a good solution for triggering an action you wish to occur whether or not you are in a digest cycle.

    By creating a custom event you are also being more efficient with your code because you are only triggering listeners subscribed to said event and NOT triggering all watches bound to the scope as you would if you invoked scope.$apply.

    $scope.$on('customEventName', function (optionalCustomEventArguments) {
       //TODO: Respond to event
    });
    
    
    $scope.$broadcast('customEventName', optionalCustomEventArguments);
    
    0 讨论(0)
提交回复
热议问题