Retain scroll position on route change in AngularJS?

后端 未结 15 1394
太阳男子
太阳男子 2020-11-30 19:49

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

相关标签:
15条回答
  • 2020-11-30 20:22

    this may solve your problem, it works for me $httpProvider.defaults.cache = true;

    0 讨论(0)
  • 2020-11-30 20:22

    I am using a custom solution in my project.

    Step 1: Obtain the position of the click on the list and save it in local storage.

    var position = document.body.scrollTop;
    localStorage.setItem("scrollPosition",position);
    

    Step 2:In the detail view set a global variable backFromDetailView to true.

    backFromDetailView = true;
    

    Step 3:On returning from the detail view page to the list. All the content is reloaded from server again up to the scrolled position.

    For this purpose bind a function in the html using the following line:

    And the controller contains the function:

    $scope.goto = function (){
        if(backFromDetailView){
             window.scrollTo(0, localStorage.getItem("scrollPosition"));
         }
    }
    

    Some disadvantages of this technique:

    1. All the content including additional content is reloaded again.

    2. In iOS, a black screen appear before scrolling to the appropriate position.

    0 讨论(0)
  • 2020-11-30 20:24

    Unlike the other answers I wanted to remember more than just scrolls, namely input field values.

    Not only that, but a lot of them assumed

    • you only wanted to remember one scrolling element (maybe you have panes or some other app-like display),
    • you have body as your scrolling element (e.g. what if you're using angular snap?),
    • or your scrolling element isn't replaced by angular (i.e. it's outside the ng-view).
    <body> <!-- doesn't scroll -->
        <div snap-drawers>..</div>
    
        <div snap-content="" history="scrollTop"> <!-- the scrolling div-->
            <header>...</header>
    
            <div ng-view>
                <input name="email" history="val"> <!-- tag with value we want remembered -->
    
                <div history="scrollLeft" history-watch="scroll" id="evenHorizontalScroll"><!--
                    custom value we want remembered.
                    NB it must have an id to be identified after angular
                    removes it from the DOM between views,
                    and since it is not a recognised default we can tell my
                    directive the jquery event function what to watch
                --></div>
            </div>
        </div>
    </body>
    

    I've written a[n unfortunately much longer] shared-scope directive that takes care of these problems.

    .directive('history', function($compile, $rootScope, $location) {
        return {
            restrict : 'A',
            replace : false,
            scope : false,
    
            controller : function($scope, $timeout) {
                //holds all the visited views
                var states = new Object();
                //the current view
                var state = null;
                //how many names have been generated where the element itself was used
                var generated = 0;
    
                //logs events if allowed
                function debug(from) {
                    //comment this to watch it working
                    //return;
    
                    console.log('StateHistory: ' + from);
                    if (from == 'went')
                        console.log(state);
                }
    
                //applies the remembered state
                function apply() {
                    var element;
                    //for each item remembered in the state
                    for (var query in state) {
                        //use the element directly, otherwise search for it
                        (state[query].element || $(query))
                            //use the appropriate function
                            [state[query].property](
                                //and set the value
                                state[query].value
                            )
                        ;
                        debug('applying:' + query + ':' + state[query].value);
                    }
    
                    //start recording what the user does from this point onward
                    $scope.ignore = false;
                }
    
                //generates a reference we can use as a map key
                $scope.generateRef = function() {
                    return '' + (++generated);
                };
    
                //views changed
                $scope.went = function() {
                    debug('went');
    
                    //set the current state
                    state = states[$location.path()];
    
                    //if we dont remember the state of the page for this view
                    if (!state)
                        //get recording!
                        state = states[$location.path()] = new Object();
    
                    //apply the state after other directives
                    //(like anchorScroll + autoscroll) have done their thing
                    $timeout(apply);
                };
    
                //one of the elements we're watching has changed
                $scope.changed = function(name, element, property, useObject) {
                    //if we're not meant to be watching right now
                    //i.e. if the user isnt the one changing it
                    if ($scope.ignore) {
                        debug('ignored');
                        return;
                    }
    
                    //if we havent recorded anything for this here yet
                    if (!state[name]) {
                        //start recording
                        state[name] = {property:property};
    
                        //and remember to leave behind a reference if the name isn't
                        //good enough (was generated)
                        if (useObject)
                            state[name].element = element;
                    }
    
                    //use the requested function to pull the value
                    state[name].value = element[property]();
    
                    debug('changed:' + name + ':' + state[name].value);
                };
    
                //initial view
                $scope.went();
    
                //subsequent views
                $rootScope.$on('$routeChangeSuccess', $scope.went);
                $rootScope.$on('$routeChangeError', $scope.went);
    
                $rootScope.$on('$routeChangeStart', function() {
                    debug('ignoring');
                    $scope.ignore = true;
                });
            },
    
            link: function (scope, element, attrs) {
                //jquery event function name
                var watch = attrs.historyWatch;
                //if not set, use these defaults
                if (!watch) {
                    switch (attrs.history) {
                    case 'val':
                        watch = 'change';
                        break;
                    case 'scrollTop':
                        watch = 'scroll';
                        break;
                    default:
                        watch = attrs.history;
                    }
                }
    
                //the css selector to re-find the element on view change
                var query = null;
                //the reference to the state remembered
                var name;
    
                //try using the id
                if (attrs.id)
                    name = query = '#' + attrs.id;
                //try using the form name
                else if (attrs.name)
                    name = query = '[name=' + attrs.name + ']';
                //otherwise we'll need to just reference the element directly
                //NB should only be used for elements not swapped out by angular on view change,
                //ie nothing within the view. Eg the view itself, to remember scrolling?
                else
                    name = scope.generateRef();
    
                //jquery value function name
                var property = attrs.history;
    
                //watch this element from here on out
                element.on(watch, function() {
                    scope.changed(name, element, property, !query);
                });
            }
        };
    })
    
    0 讨论(0)
  • 2020-11-30 20:27

    i created a directive that works on the window scroll ( it could updated to work on any element though )

    html usage

    <div ng-keep-scroll="service.scrollY">
    <!-- list of scrolling things here -->
    </div>
    

    where "service.scrollY" MUST be a variable within a service. Services retain their state and values, controllers are recreated every time they load and clear their values so you cant use them to store persistent data. the controller has a scope variable pointing to the service.

    directive js

    app.directive('ngKeepScroll', function ($timeout) {
        return function (scope, element, attrs) {
    
            //load scroll position after everything has rendered
            $timeout(function () {
                var scrollY = parseInt(scope.$eval(attrs.ngKeepScroll));
                $(window).scrollTop(scrollY ? scrollY : 0);
            }, 0);
    
            //save scroll position on change
            scope.$on("$routeChangeStart", function () {
                scope.$eval(attrs.ngKeepScroll + " = " + $(window).scrollTop());
            });
        }
    });
    
    0 讨论(0)
  • 2020-11-30 20:29

    I have used the solution of @Joseph Oster in order to create a directive. I have also taken the liberty to update the answer to use:

    • $locationChangeStart
    • $locationChangeSuccess

    as the other events are obsolete.

    Fiddle is here: http://jsfiddle.net/empie/p5pn3rvL/

    Directive source:

    angular.module('myapp', ['ngRoute'])
        .directive('autoScroll', function ($document, $timeout, $location) {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                scope.okSaveScroll = true;
    
                scope.scrollPos = {};
    
                $document.bind('scroll', function () {
                    if (scope.okSaveScroll) {
                        scope.scrollPos[$location.path()] = $(window).scrollTop();
                    }
                });
    
                scope.scrollClear = function (path) {
                    scope.scrollPos[path] = 0;
                };
    
                scope.$on('$locationChangeSuccess', function (route) {
                    $timeout(function () {
                        $(window).scrollTop(scope.scrollPos[$location.path()] ? scope.scrollPos[$location.path()] : 0);
                        scope.okSaveScroll = true;
                    }, 0);
                });
    
                scope.$on('$locationChangeStart', function (event) {
                    scope.okSaveScroll = false;
                });
            }
        };
    })
    
    0 讨论(0)
  • Based on the great answer from br2000, I updated the directive code to work with ui-router. For states with same name but different params I serialize the $state.params object to make up a unique key in the scrollPosCache object.

    .directive("keepScrollPos", function($state, $window, $timeout, $location, $anchorScroll) {
    
        // cache scroll position of each route's templateUrl
        var scrollPosCache = {};
    
        // compile function
        return function(scope, element, attrs) {
    
          scope.$on('$stateChangeStart', function() {
            // store scroll position for the current view
            if ($state.current.name) {
              scrollPosCache[$state.current.name + JSON.stringify($state.params)] = [ $window.pageXOffset, $window.pageYOffset ];
            }
          });
    
          scope.$on('$stateChangeSuccess', 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[$state.current.name + JSON.stringify($state.params)] || [ 0, 0 ];
              $timeout(function() {
                $window.scrollTo(prevScrollPos[0], prevScrollPos[1]);
              }, 0);
            }
          });
        }
      })
    
    0 讨论(0)
提交回复
热议问题