$ionicScrollDelegate issue

I’ve noticed (some topics seem to suggest its only since version 1.1.0) that the ‘size’ of my scroll-area (eg. <ion-content>) SOMETIMES does not update after elements change their size (width or height), for example if additional elements get shown via ng-show.

According to the manual, this can be fixed by calling $ionicScrollDelegate.resize(), but since I did not want to manually attach this behavior to any model-update that MIGHT trigger a element size change I implemented a simple watch directive on the height/width of an element.

app.directive('scrollSizeWatch', function($ionicScrollDelegate) {
  return {
    restrict: 'A',
    link:     function(scope, element, attr) {
      var fetchValue = function() {
        return {
          width:  $(element).width(),
          height: $(element).height()
        };
      };
      
      var valueChanged = function(value) {
        $ionicScrollDelegate.resize();
        //console.log('Size changed to', value);
      };

      scope.$watch(fetchValue, valueChanged, true);
    }
  };
});

The ‘funny’ thing I noticed is, that this still didn’t fix all occurances of this problem and a noticed that angular did not detect all size changes.
My guess is, that occasionally the size changed triggered inside some $scope.$apply() (eg. by a ng-click) actually happens after the $digest()-cicle has finished, thus beeing unnoticed by angular.
(If someone could confirm my theory I’d be really happy…)

I implemented a workaround that will start a $timeout during each $digest()-cicle, that checks the elements size after a certain delay and calls $ionicScrollDelegate.resize() if a change was detected.
(I don’t like this workaround, because it depends on the assumption that the updated size is known after a fixed amount of time after the $digest()-cicle started)

app.directive('scrollSizeWatch', function($ionicScrollDelegate, $timeout, scrollFixDelay) {
  return {
    restrict: 'A',
    link:     function(scope, element, attr) {
      // Variables to store current state
      var oldValue, promise;


      /**
       * Function: delayedCheck()
       *  Checks if the width or heights of the
       *  directives element has changed and
       *  updates the cached oldValue state.
       *
       * [SYNC]
       */
      var delayedCheck = function() {
        // Cache current value
        var newValue = {
          width:  $(element).width(),
          height: $(element).height()
        };

        // Compare old and current values
        if (newValue.width != oldValue.width || newValue.height != oldValue.height)
          $ionicScrollDelegate.resize();

        // Update old values
        oldValue = newValue;
      };


      /**
       * Function fetchValue()
       *  This will be called multiple times during each $digest()
       *  phase by angular to detect changes.
       *
       *  Note: Occasionally it can happen, that a change by angular
       *   can trigger a size (width/height) change of a html element.
       *   Rendering of such changes can sometimes happen after angulars
       *   $digest() cicle, which may cause angular to miss those changes.
       *  Workaround:
       *   Start a timer during the $digest() cicle that checks for changes
       *   after a certain amount of time has passed. Obviously this might
       *   break, if a $digest() cicle takes longer than scrollFixDelay
       *   milliseconds, but there does not seem to be a better solution.
       *
       * [SYNC & ASYNC]
       */
      var fetchValue = function() {
        // Cancel old timer and start new one (do not create a new digest cicle!)
        $timeout.cancel(promise);
        promise = $timeout(delayedCheck, scrollFixDelay, false);

        // Return width/height information for angular to watch
        return {
          width:  $(element).width(),
          height: $(element).height()
        };
      };


      /**
       * Function: valueChanged()
       *
       *
       * [SYNC]
       */
      var valueChanged = function(value) {
        $timeout.cancel(promise);
        oldValue = value;
      };


      // Watch for height/width changes
      scope.$watch(fetchValue, valueChanged, true);
    }
  };
});

Usage:

<ion-content>
  <!-- NOTE: Fetching the size of an element, eg. via height(), for some reason does not work on custom elements (using scroll-size-watch on <my-directive> does not work), they'll always return 0 -->
  <div scroll-size-watch>
    <a ng-click="showBigText()">Show Text</a>
    <span ng-show="bigTextVisible()">{{someBigText}}</span>
  </div>
</ion-content>

(Sidenote: Besides using $ionicScrollDelegate.resize(), scrolling all the way up/down and releasing touch also updates the scroll-area to the correct size, if that helps track down the issue.)