2

I've made a couple of directives to make the bootstrap popovers happen when a form field is invalid. The only hurdle I'm having was attempting to 'evaluate' an attribute value to either true or false so i can call open or close the popover. I'm an angular neophyte (just learning) but doing some research, it seemed using $scope.watch is the thing to use (and also from looking at ng-show and ng-hide code). So everything works darling until I try to call element.triggerHandler() from within $scope.watch and using bootstrap.ui.

Here is my directive:

app.directive('tooltipTriggerOn', ['$log', function($log) {
  function link(scope, element, attrs) {
    scope.$watch(attrs.tooltipShow, function(val) {
        if (val) {
          $log.info('trigger openPopup');
        } else {
          $log.info('trigger closePopup');
        }
      //if (val) element.triggerHandler('openPopup');
      //else element.triggerHandler('closePopup');
    });
  }
  return {
    restrict: 'A',
    link: link
  };
}]);

The if/else commented out makes the app run with no issues. If I enable those lines, I get javascript errors:

Error: [$rootScope:inprog] $digest already in progress
Error: a.$apply(...) is not a function

But why? This little scheme works fine when I'm not using ui.bootstrap. So:

  1. Is there a trick to use scope.$watch in my directive and not get the error?
  2. Is there a different way to 'evaluate' the directive attribute without using scope.$watch?
  3. Why is this error happening?

Here are the plunkrs:

This one demonstrates the issue with bootstrap.ui, just uncomment the offending if statement as described in the question.

plunkr

This one demonstrates the $scope.watch working without bootstrap.ui

plunkr

Any help appreciated!

- Mike

mikew
  • 912
  • 2
  • 11
  • 22

1 Answers1

2

When I come across the $digest already in progress error, and I can't rewrite the code in another way that avoids the problem, I wrap $timeout(function(){...}) around it. This causes the wrapped code to be executed after the current digest cycle (so that it's not in progress).

See also, "AngularJS : Prevent error $digest already in progress when calling $scope.$apply()" (2nd reply)

  $timeout(function(){
    if (val) element.triggerHandler('openPopup');
    else element.triggerHandler('closePopup');
  });

Working demo: http://plnkr.co/edit/STaPZI2f9eTaRhnsr6Qm?p=preview

Community
  • 1
  • 1
towr
  • 4,147
  • 3
  • 20
  • 30
  • Thanks, I did read that SO question but it didn't seem related. I see now although I'm still a bit confused as to why the error even happens and why $timeout fixes it (besides just saying it delays it to the next digest cycle - whatever that is is the grand scheme of things). Hopefully things become more clear as I continue to use angular. Thanks! – mikew Feb 09 '14 at 18:48
  • I don't entirely understand the particulars myself, but as I understand it during a digest cycle angular check all the values which have changed on the model and updates the view/DOM accordingly. And it is a problem if during that time you make further changes to the model (because it might affect parts of the view not yet updated and make things inconsistent), so they have to be postponed until angular has digested everything. – towr Feb 09 '14 at 19:05