Saturday, February 20, 2016

Angular bound model data not updated in the $on callback function

TL;DR

Using $timeout or $apply, or $evalAsync in the callback function of $on, if you need to handle some bound model and have to make sure you get it before or after it's updated

I met this one when I was trying to save map data from a google map view whenever user changes the map view.



The code somewhat looks like this.

 // In Map controller ...  
 // ... bind google map's control data to mapModel, so mapModel will update after each digest cycle.  
 // ... bind google map event "idle" to onIdle function  
 function onIdle() {
    // Broadcast event after each user interaction to the map
    $rootScope.$broadcast('map:idle', mapModel);  
 }  

 // In some other controller ...  
 $scope.$on('map:idle', function(event, mapModel){  
   console.log(mapModel);  
   saveMap(mapModel);  
 });  

I expected to get map state after I panned or zoomed the map, but instead I got the "previous one" of map state, before mapModel is updated.

The order of what happened is like:

  1. User changes map view.
  2. Process event and call handler.
  3. Digest cycle updates mapModel
A simple fact comes out here:
The Angular event flow,  $broadcast, $emit, $on, don't sync with the digest cycle.
Which means the executions of $broadcast, $emit and the registered callbacks of $on won't be scheduled before, in, or after Angular's digest cycle.

It's a bit hard for me to realize this since I reckoned most of the Angular built-in functions do sync with digest cycle implicitly.

Well it's alright whatever, I found the fast workaround is to use $timeout to get the updated map model, because $timeout always trigger it's registered function after the current digest cycle completed

 // In some other controller ...  
 $scope.$on('map:idle', function(event, mapModel){  
   $timeout(function () {  
     saveMap(mapModel);  
   }, 0, false);  
 });  

Note the third parameter of $timeout is false because I don't want to invoke another digest cycle.

No comments:

Post a Comment