Maintain the scroll position when prepending content to a list (AngularJS)

All we need is an easy explanation of the problem, so here it is.

I’ve been trying to prepend some items to a list within a scrollable container by using ng-repeat, and the recent one should be at the top of the list. I also need to maintain the scroll position if the scroll bar of the container is not at the very top when prepending content.

Here is my solution, but I still have a problem. There is always a flickering after angular has rendered prepended items in dom.

var myApp = angular.module('myApp', []);

myApp.controller('MainCtrl', function($scope, $interval, $timeout) {
  $scope.items = [];
  $interval(function() {
    var item = {
      id: Math.random(),
      text: (new Date()).toString()
    };
    $scope.items.unshift.apply($scope.items, [item]);

    var $container = $('.stream-container');
    var $topItem = $('.item:first');
    var oScrollTop = $container.scrollTop();
    var oOffset = $topItem.length ? $topItem.position().top : 0;

    $timeout(function() {
      // Executed after the dom has finished rendering
      if ($container.scrollTop() !== 0) {
        $container.scrollTop(oScrollTop + ($topItem.length ? $topItem.position().top : 0) - oOffset);
      }
    });
  }, 1000);
});
.stream-container {
  overflow-y: scroll;
  overflow-x: none;
  height: 100px;
  position: relative;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<body ng-app="myApp">
  <div class="stream-container" ng-controller="MainCtrl">
    <div class="stream">
      <div class="item" ng-repeat="item in items track by item.id">{{ item.text }}</div>
    </div>
  </div>
</body>

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

I found this post and changed $timeout to $scope.$$postDigest. Now it works as expected.

var myApp = angular.module('myApp', []);

myApp.controller('MainCtrl', function($scope, $interval, $timeout) {
  $scope.items = [];
  $interval(function() {
    var item = {
      id: Math.random(),
      text: (new Date()).toString()
    };
    $scope.items.unshift.apply($scope.items, [item]);

    var $container = $('.stream-container');
    var $topItem = $('.item:first');
    var oScrollTop = $container.scrollTop();
    var oOffset = $topItem.length ? $topItem.position().top : 0;

    $scope.$$postDigest(function() {
      // Executed after the dom has finished rendering
      if ($container.scrollTop() !== 0) {
        $container.scrollTop(oScrollTop + ($topItem.length ? $topItem.position().top : 0) - oOffset);
      }
    });
  }, 1000);
});
.stream-container {
  overflow-y: scroll;
  overflow-x: none;
  height: 100px;
  position: relative;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<body ng-app="myApp">
  <div class="stream-container" ng-controller="MainCtrl">
    <div class="stream">
      <div class="item" ng-repeat="item in items track by item.id">{{ item.text }}</div>
    </div>
  </div>
</body>

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply