Retain scroll position on route change in AngularJS?

Sample app: http://angular.github.com/angular-phonecat/step-11/app/#/phones

If you choose the last phone "Motorola charm" it will show you the details of the phone. When you navigate back with on your browser it reloads the data and scrolling is at the top.

What is the best way to automatically scroll to where is was left when navigatin back? And also, why does angular reloads the data?

I have the same "angular-phonecat" sample on my computer and I have added an infinite scroll which loads more data as you scroll. So I really dont want the user to reload 50+ items again or scrolling down for 30 seconds.


Solution 1:

I have a fiddle here that shows how to restore scroll position in the list view after a detail view; not encapsulated in a directive yet, working on that...

http://jsfiddle.net/BkXyQ/6/

$scope.scrollPos = {}; // scroll position of each view

$(window).on('scroll', function() {
    if ($scope.okSaveScroll) { // false between $routeChangeStart and $routeChangeSuccess
        $scope.scrollPos[$location.path()] = $(window).scrollTop();
        //console.log($scope.scrollPos);
    }
});

$scope.scrollClear = function(path) {
    $scope.scrollPos[path] = 0;
}

$scope.$on('$routeChangeStart', function() {
    $scope.okSaveScroll = false;
});

$scope.$on('$routeChangeSuccess', function() {
    $timeout(function() { // wait for DOM, then restore scroll position
        $(window).scrollTop($scope.scrollPos[$location.path()] ? $scope.scrollPos[$location.path()] : 0);
        $scope.okSaveScroll = true;
    }, 0);
});

The fiddle also shows fetching the list once, outside of 'ListCtrl'.

Solution 2:

Below is another version of keep-scroll-pos directive. This version

  • Remembers scroll position of each templateUrl of your $routeProvider definition.

  • Respects hash tags, e.g., #/home#section-2, will scroll to #section-2 not previous scroll position.

  • Is easy to use, as it is self-contained, and stores scroll positions internally.

Example of html use:

<div ng-view keep-scroll-pos></div>

The code for keepScrollPos directive is below:

"use strict";

angular.module("myApp.directives", [])

.directive("keepScrollPos", function($route, $window, $timeout, $location, $anchorScroll) {

    // cache scroll position of each route's templateUrl
    var scrollPosCache = {};

    // compile function
    return function(scope, element, attrs) {

        scope.$on('$routeChangeStart', function() {
            // store scroll position for the current view
            if ($route.current) {
                scrollPosCache[$route.current.loadedTemplateUrl] = [ $window.pageXOffset, $window.pageYOffset ];
            }
        });

        scope.$on('$routeChangeSuccess', function() {
            // if hash is specified explicitly, it trumps previously stored scroll position
            if ($location.hash()) {
                $anchorScroll();

            // else get previous scroll position; if none, scroll to the top of the page
            } else {
                var prevScrollPos = scrollPosCache[$route.current.loadedTemplateUrl] || [ 0, 0 ];
                $timeout(function() {
                    $window.scrollTo(prevScrollPos[0], prevScrollPos[1]);
                }, 0);
            }
        });
    }
});

To disregard previously stored scroll position, and to force to scroll to the top, use pseudo hash tag: #top, e.g., href="#/home#top".

Alternatively, if you prefer to just always scroll to the top, use built-in ng-view autoscroll option:

<div ng-view autoscroll></div>