0

I have this example which a basic list with the option to remove items.

When the user tries to remove something, a confirmation is required. But also, to demonstrate which item will be deleted I've changed the table row colour conditionally.

The problem is, I could not make the colour of the selected row change without using $scope.$apply() before the confirm() statement.

$scope.removeEntry = function(index) {
    $scope.entries[index].toBeRemoved = true;
    $scope.$apply();
    if (confirm("Are you sure you want to delete this item?") === true) {
        $scope.entries.splice(index, 1);
    }else{
        $scope.entries[index].toBeRemoved = false;
    }
};

But this gives me:

Error: [$rootScope:inprog] $apply already in progress

Am I missing something or is there any better way to do it and preventing this?

I've already tried almost all suggestions on this answer without success.

Community
  • 1
  • 1
Patrick Bard
  • 1,804
  • 18
  • 40

2 Answers2

2

A solution to your case is to use $timeout from angular: http://plnkr.co/edit/ZDkGMqmwtxh7HSvBEWYp?p=preview

Here is a post on the $apply vs $timeout discussion: Angular $scope.$apply vs $timeout as a safe $apply

$scope.removeEntry = function(index) {
    $scope.entries[index].toBeRemoved = true;
    $timeout(function() {
      if (confirm("Are you sure you want to delete this item?") === true) {
        $scope.entries.splice(index, 1);
      }else{
          $scope.entries[index].toBeRemoved = false;
      }
    })
};

You must have messed up in implementing it properly.

Community
  • 1
  • 1
Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
  • Hahahah, I am so dumb. I did exactly this, but didn't work because I forgot to inject `$timeout` on the controller, obviously. I feel bad now – Patrick Bard Jul 08 '15 at 12:32
1

One more solution to help you out this problem. You could use $evalAsync from Angular.

var app = angular.module('plunker', [])

.controller('ListController', ['$scope', '$timeout', function($scope, $timeout) {
    $scope.entries = [{name:"potatoes"},
                      {name:"tomatoes"},
                      {name:"flour"},
                      {name:"sugar"},
                      {name:"salt"}];

    $scope.removeEntry = function(index) {
        $scope.entries[index].toBeRemoved = true;
        $evalAsync(function() {
          if (confirm("Are you sure you want to delete this item?") === true) {
            $scope.entries.splice(index, 1);
          }else{
              $scope.entries[index].toBeRemoved = false;
          }
        })
    };
}]);

Choosing between $evalAsync and $timeout depends on your circumstance:

  • If code is queued using $evalAsync from a directive, it should run after the DOM has been manipulated by Angular, but before the browser renders.

  • If code is queued using $evalAsync from a controller, it should run before the DOM has been manipulated by Angular (and before the browser renders) -- rarely do you want this

  • if code is queued using $timeout, it should run after the DOM has been manipulated by Angular, and after the browser renders (which may cause flicker in some cases)

Patrick Bard
  • 1,804
  • 18
  • 40
ngLover
  • 4,439
  • 3
  • 20
  • 42