AngularJS: Why and when calling $timeout is required?

2.1k Views Asked by At

Live Demo

I created a confirmationDialogHeader directive which is used like so:

<ul>
  <li ng-repeat="item in items">
    {{item}}
    <button class="btn btn-mini" 
            confirmation-dialog-header="Delete Item" 
            confirmation-dialog-action="deleteItem($index)">
      Delete
    </button>
  </li> 
</ul>

When the button is clicked, it is supposed to open a confirmation dialog, and if user confirms, it should call deleteItem($index), and close the dialog.

My problem is with the last bit: closing the dialog.

Here is the controller (CoffeeScript):

app.controller 'AppCtrl', ['$scope', '$q', '$timeout', ($scope, $q, $timeout) ->
  $scope.items = ['Item 1', 'Item 2'] 
  $scope.deleteItem = (index) ->                         
    deferred = $q.defer()
    $timeout ->
      $scope.items.splice(index, 1)   # All good if this line is removed
      deferred.resolve()
    , 1000
    deferred.promise
]

and here is the directive:

app.directive 'confirmationDialogHeader', ['$compile', '$q', ($compile, $q) ->
  restrict: 'A'
  scope:
    confirmationDialogAction: '@'
  controller: ($scope) ->
    $scope.onConfirm = ->
      $scope.$parent.$eval($scope.confirmationDialogAction).then ->
        $scope.confirmationDialog = off   # Why this doesn't close the dialog?
  link: (scope, element, attrs) -> 
    $confirmationDialog = $ """
      <div modal="confirmationDialog">
        <div class="modal-header">
          <h4>#{attrs.confirmationDialogHeader}</h4>
        </div>
        <div class="modal-body">
          Are you sure?
        </div>
        <div class="modal-footer">
          <button class="btn" ng-click="confirmationDialog = false">
            Cancel
          </button>
          <button class="btn" ng-click="onConfirm()">
            Yes
          </button>
        </div>
      </div>
    """

    $(document.body).append($confirmationDialog)
    $compile($confirmationDialog)(scope)

    $(element).click ->
      scope.$apply ->
        scope.confirmationDialog = yes  

]

As you can see, the dialog is not closed after deleteItem() finishes and $scope.confirmationDialog = off is executed.

The interesting part is that, if $scope.items.splice(index, 1) is wrapped with $timeout, the dialog is closed properly!

$timeout ->
  $scope.items.splice(index, 1)

Why this $timeout wrapping is necessary here?

Are there any guidelines about when should we wrap code with $timeout?


Working version of this directive that uses $dialog.


1

There are 1 best solutions below

4
On

Are there any guidelines about when should we wrap code with $timeout?

$timeout is used as a replacement to the native setTimeout. The advantage over the native timeout is that it runs the callback in a try/catch block and any errors occurred will be forwarded to your applications exceptionHandler service.

However from my playing with your demo that is not the issue I am seeing and the code can be simplified.

Update I was hoping to get your specific implementation using modal dialog working, but I didn't have much luck. So implemented it using the $dialog service rather than the modal directive. Maybe it will help - here it is

Also, with your original version I found that the scope was being updated when you clicked yes to close the dialog and it was happening within a digest so it should have fired any watchers and thus close the dialog. In that regard I cannot see a reason why it was not working for whatever reason in my browser it made no difference whether or not timeout was used.