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
I haven't used it before, but angular has a $anchorScroll service. As to reloading the data, you could cache it using $cacheFactory, or store the data on a higher scope.
Great solution by @br2000.
However unfortunately my page that I was scrolling back to, was still loading data from backend to a long list when the directive tried to restore the position.
So obviously it failed to restore the scroll position. I solved it by using $interval
instead of $timeout
and gave it 20 repetitions with 300ms timeout
. I stored the promise returned from $interval
and then checked inside the $interval
function if current position is now the same as stored position and if yes, I call a scope method that cancels the $interval - $interval.cancel(promise).
Additionally, initially my pageYOffset
and pageXOffset
were always 0, because overflow-x: hidden
was applied to the root div
in the DOM
. I solved it by wrapping the root div
inside another div
on which I then placed this directive.
I've found another simple way to solve this issue:
var scrollValue = $(window).scrollTop();
$rootScope.$on("$routeChangeStart", function() {
scrollValue = $(window).scrollTop();
});
$rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
setTimeout(function() { $(window).scrollTop(scrollValue); }, 0);
});
Just put it in .run().
This way, setting timeout value to 0 it still works, but runs after the page is rendered (without timeout function it runs before the content (i.e. template or data loading) is rendered, making the function useless.
If you fetch data from some API, you can wrap the timeout in a function in $rootScope and run it after successful request.
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>
I made a version that works with any overflowed element, not just the document body:
.directive("keepScrollPos", function($route, $timeout, $location, $anchorScroll) {
// cache scroll position of each route's templateUrl
var cache = {};
return {
restrict : 'A',
link: function($scope, elements, attrs){
$scope.$on('$routeChangeStart', function() {
// store scroll position for the current view
if($route.current)
cache[$route.current.loadedTemplateUrl + ':' + attrs.keepScrollPos] = [elements[0].scrollLeft, elements[0].scrollTop];
});
$scope.$on('$routeChangeSuccess', function(){
// if hash is specified explicitly, it trumps previously stored scroll position
if($location.hash()){
$anchorScroll();
return;
}
// else get previous scroll position and apply it if it exists
var pos = cache[$route.current.loadedTemplateUrl + ':' + attrs.keepScrollPos];
if(!pos)
return;
$timeout(function(){
elements[0].scrollLeft = pos[0];
elements[0].scrollTop = pos[1];
}, 0);
});
}
}
})
Use it like:
<div keep-scroll-pos="some-identifier"> ... </div>
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'.