ivanhoe
ivanhoe

Reputation: 111

avoid "$digest already in progress" error

I've defined the following directive which wraps the pagination directive provided by UI Bootstrap

angular.module('my-module')
  .directive('myPagination', ['$filter', '$window', function ($filter, $window) {

    return {
      restrict: 'EA',
      scope: {
        itemsPerPage: '=',
        totalItems: '=',
        page: '='
      },
      link: function (scope, element, attrs) {
        scope.hiddenElement = true;
        var totalMargin = 200, widthByElement = 41;

        scope.getWindowDimensions = function () {
          return {
            'w': element.width()
          };
        };

        scope.$watch(scope.getWindowDimensions, function (newWidth, oldWidth) {
          // calculate how many page buttons fit in the pagination horizontal bar
          scope.maxSize = parseInt($filter('number')((newWidth.w - totalMargin) / widthByElement, 0));
        }, true);

        angular.element($window).bind('resize', function () {
          // "$digest already in progress" thrown by the following line
          scope.$apply();  
        });

      },
      template: '<div class="text-center paginationSelfCare"  ng-show="totalItems > itemsPerPage">' +
      '<pagination ng-show="totalItems/itemsPerPage <= maxSize" total-items="totalItems" items-per-page="itemsPerPage" ng-model="page" max-size="maxSize" class="pagination pagination-sm" ></pagination>' +
      '<pagination ng-show="totalItems/itemsPerPage > maxSize" total-items="totalItems" boundary-links="true" direction-links="true" previous-text="<" next-text=">" first-text="<<" last-text=">>" items-per-page="itemsPerPage" ng-model="page" max-size="maxSize" class="pagination pagination-sm" ></pagination>' +
      '</div>'
    };
  }]);

If this directive is running in one browser tab (A), and I switch to a different tab (B), then when I switch back to A, I see the following error in the browser console:

Error: [$rootScope:inprog] $digest already in progress http://errors.angularjs.org/1.4.4/$rootScope/inprog?p0=%24digest minErr/<@http://127.0.0.1:9000/bower_components/angular/angular.js:68:12 beginPhase@http://127.0.0.1:9000/bower_components/angular/angular.js:16273:1 $RootScopeProvider/this.$gethttp://127.0.0.1:9000/bower_components/angular/angular.js:16014:11 .link/<@http://127.0.0.1:9000/scripts/directives/pagination.js:30:11

Line 30 of pagination.js is the line marked with a comment above

scope.$apply(); 

Is there something I can check before calling scope.$apply(); in order to avoid this issue? I'm running Angular version 1.4.4

Upvotes: 2

Views: 3053

Answers (2)

Pankaj Parkar
Pankaj Parkar

Reputation: 136134

You could try $socpe.$applyAsync() instead of $scope.$apply() which will never conflict with currently running digest cycle.

$apply on $scope directly tried to run digest cycle when you run it directly. It doesn't check for if any digest cycle is in progress, and you run the digest loop, it throws $digest already in progress.

Though you could fix this issue by using wrapping your code $timeout function, but I'd prefer to go for $applyAsync method of scope.

$timeout does run digest cycle but before running it, it checks for if any digest is in progress, then run place this digest cycle call in queue and run it once the running digest cycle complete.

$applyAsync is a performance wise improved version of running digest cycle, when you ran this method, it first check for any digest cycle is running or not. if there any running digest cycle found it place that block of in that digest cycle (That means you reduced one digest cycle).

Reference Link

Upvotes: 6

Petr Averyanov
Petr Averyanov

Reputation: 9476

Your watch is very strange. This is usual way:

angular.element($window).bind('resize', function () {
  // There are a lot of resize events, we do not need to change smth on each 
  // Once per some time is ok.
  if (attrs.lastResize) {
      $timeout.cancel(attrs.lastResize)
  }

  attrs.lastResize = $timeout(function() {
      // actual code like
      // like scope.maxSize = calc();
  }, 75)
});

You dont need apply here, since you have $timeout.

Upvotes: 0

Related Questions