implementing a lazy load and infinite scroll in AngularJS

I am planning to refactor this into a more simple, modular directive, but before doing so, I just want to describe how I approached implementing a lazy load and infinite scroll on a project I picked up. It's a dashboard for tracking individual and group progress. (Sidenote: I also would like to modularize it into an easy, near-instant dashboard library. The version I'm currently building interfaces with 2 external APIs (Github and Keen.io), as well as an API that I implemented in a nodeJS server.)

This implemenation is dependent on an API that responds once with the entire data set that will need to be fetched. Originally, I designed the api to respond to multiple calls from the client because I thought that returning too much would negetivley impact browser performance. But, that was naive. The browser can handle this without breaking a sweat.

///////////////////////
// listController.js //
///////////////////////

angular.module('myApp').controller('listController', ['$scope','dataService','$timeout',  function($scope, dataService, $timeout){

  $scope.data = {};
  $scope.spinner = {};
  $scope.data.mongo = [];

  // used to show and hide the table element that will display the data
  $scope.isLoading = true;

  // used to manipulate the element containing the spinner once the data table
  // is displayed
  $scope.spinner.hide = false;

  // this will store the bulk of the data from the initial response.
  // then we will splice items off as needed
  var cache = { data: { mongo: [] } };

  var getData = function(){

    // check that the view-bound data set is empty
    if ($scope.data.mongo.length < 1){

      dataService.getData()
        .then(function(res){

          // display the table element
          $scope.isLoading = false;

          // declare the $timeout with a reference in order to cancel
          // it later. the $timeout invokes the AngularJS digest cycle
          // and will update the view bound to the data
          var appendDataTimer = $timeout(function(){
            cache.data.mongo = res.data.mongo;

            // splice some data to append to the view
            $scope.data.mongo = $scope.data.mongo.concat(cache.data.mongo.splice(0, 12));

            // assign some other data hashes from the response to the $scope
            // so they can be referenced in the view
            $scope.data.gh = res.data.gh;
            $scope.data.keen = res.data.keen;

            // remove the spinner from the view
            $scope.spinner.hide = true;

          },150);

          // invoke the timeout and remove it when the promise resolves
          appendDataTimer.then(function(){
            $timeout.cancel(appendDataTimer);
          })
      });

    } else {
      // the view contained data, so continue to splice from the cache and add // to the view data
      $scope.data.mongo = $scope.data.mongo.concat(cache.data.mongo.splice(0, 12));

      // delays feel more 'magical'
      $timeout(function(){
        $scope.spinner.hide = true;
      },1000);
    }
  };

  // to be invoked on the scroll event in our directive, which will be
  // triggered once we scroll through some data that is already displayed
  $scope.loadMore = function(){
    $scope.spinner.hide = false;
    var loadTimer = $timeout(function(){
      getData();
    }, 2000)
    loadTimer.then(function(){
      $timeout.cancel(loadTimer);
    });
  };

  // invoke the retrieval of data
  getData();
}])
/////////////////////////////////////////
// _list_partial.html (without styles) //
/////////////////////////////////////////

<table class="table table-striped table-hover" ng-if="!isLoading" when-scrolled="loadMore()">  
   <thead>
     <tr>
       <td>NAME</td>
       <td>STATS</td>
       <td>COMMENTS</td>
     </tr>
   </thead>
    <tbody>
      <tr ng-repeat="datum in data.mongo">
        <td>
          <span>{{ datum.First }} {{ datum.Last }}</span><br>
        </td>
        <td class="cohort-stats-col cohort-score"> some stats </td>
        <td ><textarea rows="3">{{ datum.comments }}</textarea></td>
      </tr>
    </tbody>
  </table>
  <div ng-hide="spinner.hide">
    <img src="img/spinner.gif">
  </div>
//////////////////////////
// lazyLoadDirective.js //
//////////////////////////

angular.module('myApp')  
.directive('whenScrolled', function($window, $timeout) {
  return {
    restrict: "A",
    link: function(scope, element, attr) {
      var top = angular.element($window)[0].screenTop;
      var origHeight = angular.element($window)[0].screen.height;
      var height = (origHeight * 0.9);

      // bind the digest cycle to be triggered by the scroll event
      // when it exceeds a threshold
      angular.element($window).bind('scroll', function() {
        if (angular.element($window)[0].scrollY >= (height)) {

          // show the spinner when triggered
          scope.spinner.hide = !scope.spinner.hide;

          angular.element($window)[0].requestAnimationFrame(function(){
            // invoke the function passed into the 'whenScrolled' attribute
            scope.$apply(attr.whenScrolled);

            // increment the threshold
            height += (origHeight * 1.5);
          })
        }
      });
    }
  }
})