Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

infinite scroll from the top (reverse) in AngularJS

Tags:

angularjs

I am trying to do a reverse infinite scroll. I have a comment list where I receive the last 10 most recent comments and want the user to be able to scroll up to retrieve the next 10 - similar to FB where it shows the most recent comments with a 'get previous' link, but via scroll event rather than a link.

I started with http://jsfiddle.net/vojtajina/U7Bz9/ and tried to modify it to a reverse infinite scroll and pretty quickly ended up with something like this:

  function Main($scope, $timeout) {
    $scope.items = [];

    var counter = 0;
    $scope.loadMore = function() {
      // simulate an ajax request
      $timeout( function() {
        for (var i = 0; i < 5; i++) {
          $scope.items.unshift({id: counter});
          counter += 10;
        }}, 1000);
    };

    $scope.loadMore();
  }

  angular.module('scroll', []).directive('whenScrolled', ['$timeout', function($timeout) {
    return function(scope, elm, attr) {
      var raw = elm[0];

      $timeout(function() {
        raw.scrollTop = raw.scrollHeight;
      }, 1100);

      elm.bind('scroll', function() {
        // note: if test for < 100 get into infinite loop due to 
        // the delayed render
        if (raw.scrollTop === 0) {
          var sh = raw.scrollHeight
          scope.$apply(attr.whenScrolled);
          // the items are not loaded and rendered yet, so
          // this math doesn't work
          raw.scrollTop = raw.scrollHeight - sh;
        }
      });
    };
  }]);
  ​

http://jsfiddle.net/digger69/FwWqb/2/

The issue is that when the next 10 items are retrieved, they are added to the top of the list and the entire list re-renders, and the item that was at the of the list is completely scrolled out of view. In the fiddle item "40" is at the top and when you scroll (down slightly) and then up to trigger the scrolled, item "90" is at the top. I'm looking for a good strategy to keep "40" at the top of the scroll area after it has rendered.

Note: In the fiddle I was able to get it to work by saving off the top li in the scroll event and calling scrollIntoView() until I added the timeout to simulate the ajax call. With the timeout the top li is scrolled into view before the request has come back and the new elements are rendered :/

var top = elm.find("li")[0];
scope.$apply(attr.whenScrolled);
top.scrollIntoView();
like image 513
Mark Nadig Avatar asked Dec 07 '12 00:12

Mark Nadig


1 Answers

You can try something like this: http://jsfiddle.net/mNFmf/4/

This will scroll to the bottom of the div:

$timeout(function() {
    raw.scrollTop = raw.scrollHeight;          
});    

And this will keep the div from scrolling up to the first item on the list:

var sh = raw.scrollHeight
scope.$apply(attr.whenScrolled);
raw.scrollTop = raw.scrollHeight - sh;

Update

To overcome the ajax request problem, try to use promises.

http://jsfiddle.net/mNFmf/8/

The loader would look something like this:

$scope.loadMore = function() {
  var deferred = $q.defer();

  // do ajax request here and after getting the result call:   
  deferred.resolve();

  return deferred.promise;
};

And on the other side:

loadMore.then(function() { 
  /* do whatever you want after the ajax request has been fulfilled */ 
});
like image 70
bmleite Avatar answered Oct 21 '22 19:10

bmleite