7

This is my first question on SO.

When I splice an element of an array on the scope, that change is not reflected, when done in a callback of bootbox.js.

Works:

$scope.deleteA = function() {
    if (confirm("Really delete Item 3?")) {
      $scope.itemsA.splice(2, 1);
    }
}

Does not work:

$scope.deleteB = function() {
    bootbox.confirm("Really delete Item 3?", function(answer) {
      if (answer === true) {
        $scope.itemsB.splice(2, 1);
      }
    });
}

I'm mainly interested in understanding the why. This is much more important to me than having a fancy workaround.

I created a Plunker to show the effect

georgeawg
  • 48,608
  • 13
  • 72
  • 95
Danny Soul
  • 73
  • 5
  • 2
    in bootbox callback angular not know that something changes, that's why not update view – Grundy Mar 19 '16 at 10:10
  • Consider using [ng-boot-box](https://stackoverflow.com/tags/ng-boot-box/info), an AngularJS wrapper for Bootbox.js – georgeawg Jun 13 '18 at 17:52

2 Answers2

8

Any change happens with angular scope variable from ouside world of angular, doesn't intimate angular digest system to run digest cycle for updating binding.
In bootbox callback angular not know that something change, that's why not update view.
For solving this issue, you need to kick off digest cycle manually by using $apply method, or $timeout service, like this

bootbox.confirm("Really delete Item 3?", function(answer) {
  if (answer === true) {
    $scope.$apply(function(){
        $scope.itemsB.splice(2, 1);
    });
  }
});
Grundy
  • 13,356
  • 3
  • 35
  • 55
  • Hello @Grundy. Thank you for your answer. I see, that this solution works, but I still don't understand WHY angular 'sees' the changes made, when they are done in a scope-function, but doesn't see them, when they are done in a function, which is also defined inside a scope-function. Where does the world of Angular stop and why? – Danny Soul Mar 20 '16 at 14:00
  • @DannySoul, because on every users event, like `click`, `change`, `keyup` and etc angular run digest loop automatically, and update view if needed, in your case - angular not run this after bootbox callback, and you should run it manually – Grundy Mar 20 '16 at 14:04
  • @Grundy, thanks for your answer again. But I still don't really understand it. Let's assume `$scope.name` is a String with a name in it. It is known to angular. If I change the value of `$scope.name` from within a function `$scope.myFunction` it is reflected in the view. If I change the value from within a function that I define inside of `$scope.myFunction` (`setTimeout` for example) it is not reflected. Why is angular able to see it in one case but not in the other. Why does the **calling** function matter? – Danny Soul Mar 20 '16 at 15:03
  • @DannySoul, try read a bit more about [HTML Compiler](https://docs.angularjs.org/guide/compiler) and [angular scope life cycle](https://docs.angularjs.org/guide/scope#scope-life-cycle) – Grundy Mar 20 '16 at 17:03
0

Safe method to apply scope change would be using $timeout service.

bootbox.confirm("Really delete Item 3?", function(answer) {
  if (answer === true) {
    $timeout($scope.itemsB.splice(2, 1));
  }
});

So you dont need to worry about $digest phase and your $apply will be one high call stack take a look at AngularJS Wiki - Anti-Patterns point -2 .

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

georgeawg
  • 48,608
  • 13
  • 72
  • 95
Juned Lanja
  • 1,466
  • 10
  • 21
  • The use of `$timeout` is a [code smell](https://en.wikipedia.org/wiki/Code_smell), a symptom of a deeper problem. Keep in mind that in most places (controllers, services) `$apply` has already been called for you by the directive which is handling the event. An explicit call to `$apply` is needed only when implementing custom event callbacks, or when working with third-party library callbacks. – georgeawg Jun 13 '18 at 18:03