When to use $apply within an event handler?

273 views
Skip to first unread message

Frank Schwieterman

unread,
Mar 14, 2014, 4:59:04 PM3/14/14
to ang...@googlegroups.com
  I'm having a little trouble understanding how to use $apply cleanly.  Suppose I have code that broadcasts and handles an event "updateFoo".  Suppose that "updateFoo" can come from two places: one external component [[A]] that calls $rootScope.$broadcast('updateFoo') outside of any $apply block.  Suppose an AngularJS based controller [[B]] also calls $rootScope.$broadcast('updateFoo') in a click handler, so it is within an existing $apply block.
  
  Now suppose a 2nd $scoped component [[C]] wants to handle $on('updateFoo'), updating a $scope value that affects the rendering of [[CC]].  If [[C]]'s event handler does not call $apply, then there will not be a digest cycle updating the components rendering when [[A]] broadcasts.  If [[C]]'s event handler does call $apply, an error will be thrown when [[B]] broadcasts that event, because the broadcast happens within another $apply context.  The error message is something like '$apply already in progress'.
  
  So how should [[C]] deal with $apply when modifying $scope?  I feel like I'm doing something wrong, there is poor separation of concerns if [[C]] has to consider whether [[A]] or [[B]] broadcast the event.

  I have seen some safe $apply implementations suggested online.  This feels wrong though, the fact that I need to bypass the frameworks behavior makes me think I am misunderstanding something.  I am on Angular 1.2.14.

Thanks.

Sander Elias

unread,
Mar 15, 2014, 3:47:07 AM3/15/14
to ang...@googlegroups.com
Hi Frank,

You should not need an $apply at all in the scenario you are describing. If something does not update after an broadcast, there is something off in the way you handle it.
You need an apply only if you are handling DOM generated events on your own. (should be in a directive!). 
Or if you need to handle async stuff that does not come from within Angular. (this include using setTimeout!)
Can you build a plunk in that simulates the problem you are having? If so, I will take a look, and will offer some guidance.

Regards
Sander

Frank Schwieterman

unread,
Mar 15, 2014, 1:01:21 PM3/15/14
to ang...@googlegroups.com
  Thanks Sander.  Here is a JsFiddle that demonstrates the issue (with my understanding, I'm sure;) : http://jsfiddle.net/fschwiet/wQA37/

  There are two buttons that call event updateFoo.  The second is using setTimeout, and in that case the $on call does not update the view when $scope is modified.  Note that if you click the second one again it does update the view, but that is the digest change detected by the click handler and not the setTimeout completion/ $broadcast.


--
You received this message because you are subscribed to a topic in the Google Groups "AngularJS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/angular/xaZcuKB7IlA/unsubscribe.
To unsubscribe from this group and all its topics, send an email to angular+u...@googlegroups.com.
To post to this group, send email to ang...@googlegroups.com.
Visit this group at http://groups.google.com/group/angular.
For more options, visit https://groups.google.com/d/optout.

Justin Walsh

unread,
Mar 17, 2014, 2:34:56 AM3/17/14
to ang...@googlegroups.com
Hi Frank

Strictly speaking, setTimeout is not an 'angular' function, so you would either need to
  1. Inject the $timeout service and use that in preference
  2. Call $scope.$apply as follows:
$scope.submitFoo2 = function() {
setTimeout(function() {
$scope.$apply(function() {
$rootScope.$broadcast('fooChanged', ++nextValue);
});
}, 0);
};

Having said that, I am left wondering why it works second time around....

Frank Schwieterman

unread,
Mar 17, 2014, 3:03:41 AM3/17/14
to ang...@googlegroups.com
Thanks for looking into this. Using $timeout certainly works in
this case, that is good to know. I used setTimeout in this example,
at other times I might have the same issue interacting with a 3rd
party asynchronous operation that isn't wrapped in AngularJS.
$scope.$apply seems to work in this case, but that feels awkward
because presumably the digest of the $scope broadcasting the event
will be done, rather than the $scope receiving the event.
It does work in jsfiddle though, so that is good. I wonder if
anyone can explain that?
Reply all
Reply to author
Forward
0 new messages