Retain scroll position on route change in AngularJS?

后端 未结 15 1450
太阳男子
太阳男子 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: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).
     
        
    ..
    ...

    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);
                });
            }
        };
    })
    

提交回复
热议问题