Home » Angularjs » Changing route doesn't scroll to top in the new page

Changing route doesn't scroll to top in the new page

Posted by: admin November 2, 2017 Leave a comment

Questions:

I've found some undesired, at least for me, behaviour when the route changes.
In the step 11 of the tutorial http://angular.github.io/angular-phonecat/step-11/app/#/phones
you can see the list of phones. If you scroll to the bottom and click on one of the latest, you can see that the scroll isn't at top, instead is kind of in the middle.

I've found this in one of my apps too and I was wondering how can I get this to scroll to the top. I can do it mannually, but I think that there should be other elegant way to do this which I don't know.

So, is there an elegant way to scroll to the top when the route changes?

Answers:

The problem is that your ngView retains the scroll position when it loads a new view. You can instruct $anchorScroll to “scroll the viewport after the view is updated” (the docs are a bit vague, but scrolling here means scrolling to the top of the new view).

The solution is to add autoscroll="true" to your ngView element:

<div class="ng-view" autoscroll="true"></div>

Questions:
Answers:

Just put this code to run

$rootScope.$on("$routeChangeSuccess", function (event, currentRoute, previousRoute) {

    window.scrollTo(0, 0);

});

Questions:
Answers:

After an hour or two of trying every combination of ui-view autoscroll=true, $stateChangeStart, $locationChangeStart, $uiViewScrollProvider.useAnchorScroll(), $provide('$uiViewScroll', ...), and many others, I couldn’t get scroll-to-top-on-new-page to work as expected.

This was ultimately what worked for me. It captures pushState and replaceState and only updates scroll position when new pages are navigated to (back/forward button retain their scroll positions):

.run(function($anchorScroll, $window) {
  // hack to scroll to top when navigating to new URLS but not back/forward
  var wrap = function(method) {
    var orig = $window.window.history[method];
    $window.window.history[method] = function() {
      var retval = orig.apply(this, Array.prototype.slice.call(arguments));
      $anchorScroll();
      return retval;
    };
  };
  wrap('pushState');
  wrap('replaceState');
})

Questions:
Answers:

This code worked great for me .. I hope it will also work great for you ..
All you have to do is just inject $anchorScroll to your run block and apply listener function to the rootScope like I have done in the below example ..

 angular.module('myAngularApp')
.run(function($rootScope, Auth, $state, $anchorScroll){
    $rootScope.$on("$locationChangeSuccess", function(){
        $anchorScroll();
    });

Here’s the calling order of Angularjs Module:

  1. app.config()
  2. app.run()
  3. directive's compile functions (if they are found in the dom)
  4. app.controller()
  5. directive's link functions (again, if found)

RUN BLOCK get executed after the injector is created and are used to kickstart the application.. it means when you redirected to the new route view ,the listener in the run block calls the

$anchorScroll()

and you can now see the scroll starts to the top with the new routed view 🙂

Questions:
Answers:

Here is my (seemingly) robust, complete and (fairly) concise solution. It uses the minification compatible style (and the angular.module(NAME) access to your module).

angular.module('yourModuleName').run(["$rootScope", "$anchorScroll" , function ($rootScope, $anchorScroll) {
    $rootScope.$on("$locationChangeSuccess", function() {
                $anchorScroll();
    });
}]);

PS I found that the autoscroll thing had no effect whether set to true or false.

Questions:
Answers:

Using angularjs UI Router, what I’m doing is this:

    .state('myState', {
        url: '/myState',
        templateUrl: 'app/views/myState.html',
        onEnter: scrollContent
    })

With:

var scrollContent = function() {
    // Your favorite scroll method here
};

It never fails on any page, and it is not global.

Questions:
Answers:

FYI for for anyone coming across the problem described in the title (as I did) who is also using
the AngularUI Router plugin…

As asked and answered in this SO question, the angular-ui router jumps to the bottom of the page when you change routes.
Can't figure out why page loads at bottom? Angular UI-Router autoscroll Issue

However, as the answer states, you can turn off this behavior by saying autoscroll="false" on your ui-view.

For example:

<div ui-view="pagecontent" autoscroll="false"></div>
<div ui-view="sidebar" autoscroll="false"></div> 

http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.directive:ui-view

Questions:
Answers:

you can use this javascript

$anchorScroll()

Questions:
Answers:

If you use ui-router you can use (on run)

$rootScope.$on("$stateChangeSuccess", function (event, currentState, previousState) {
    $window.scrollTo(0, 0);
});

Questions:
Answers:

I found this solution. If you go to a new view the function gets executed.

var app = angular.module('hoofdModule', ['ngRoute']);

    app.controller('indexController', function ($scope, $window) {
        $scope.$on('$viewContentLoaded', function () {
            $window.scrollTo(0, 0);
        });
    });

Questions:
Answers:

Setting autoScroll to true did not the trick for me, so I did choose another solution. I built a service that hooks in every time the route changes and that uses the built-in $anchorScroll service to scroll to top. Works for me :-).

Service:

 (function() {
    "use strict";

    angular
        .module("mymodule")
        .factory("pageSwitch", pageSwitch);

    pageSwitch.$inject = ["$rootScope", "$anchorScroll"];

    function pageSwitch($rootScope, $anchorScroll) {
        var registerListener = _.once(function() {
            $rootScope.$on("$locationChangeSuccess", scrollToTop);
        });

        return {
            registerListener: registerListener
        };

        function scrollToTop() {
            $anchorScroll();
        }
    }
}());

Registration:

angular.module("mymodule").run(["pageSwitch", function (pageSwitch) {
    pageSwitch.registerListener();
}]);

Questions:
Answers:

All of the answers above break expected browser behavior. What most people want is something that will scroll to the top if it’s a “new” page, but return to the previous position if you’re getting there through the Back (or Forward) button.

If you assume HTML5 mode, this turns out to be easy (although I’m sure some bright folks out there can figure out how to make this more elegant!):

// Called when browser back/forward used
window.onpopstate = function() { 
    $timeout.cancel(doc_scrolling); 
};

// Called after ui-router changes state (but sadly before onpopstate)
$scope.$on('$stateChangeSuccess', function() {
    doc_scrolling = $timeout( scroll_top, 50 );

// Moves entire browser window to top
scroll_top = function() {
    document.body.scrollTop = document.documentElement.scrollTop = 0;
}

The way it works is that the router assumes it is going to scroll to the top, but delays a bit to give the browser a chance to finish up. If the browser then notifies us that the change was due to a Back/Forward navigation, it cancels the timeout, and the scroll never occurs.

I used raw document commands to scroll because I want to move to the entire top of the window. If you just want your ui-view to scroll, then set autoscroll="my_var" where you control my_var using the techniques above. But I think most people will want to scroll the entire page if you are going to the page as “new”.

The above uses ui-router, though you could use ng-route instead by swapping $routeChangeSuccess for$stateChangeSuccess.

Questions:
Answers:

A little late to the party, but I’ve tried every possible method, and nothing worked correctly. Here is my elegant solution:

I use a controller that governs all my pages with ui-router. It allows me to redirect users who aren’t authenticated or validated to an appropriate location. Most people put their middleware in their app’s config, but I required some http requests, therefore a global controller works better for me.

My index.html looks like:

<main ng-controller="InitCtrl">
    <nav id="top-nav"></nav>
    <div ui-view></div>
</main>

My initCtrl.js looks like:

angular.module('MyApp').controller('InitCtrl', function($rootScope, $timeout, $anchorScroll) {
    $rootScope.$on('$locationChangeStart', function(event, next, current){
        // middleware
    });
    $rootScope.$on("$locationChangeSuccess", function(){
        $timeout(function() {
            $anchorScroll('top-nav');
       });
    });
});

I’ve tried every possible option, this method works the best.

Questions:
Answers:

None of the answer provided solved my issue. I am using an animation between views and the scrolling would always happen after the animation. The solution I found so that the scrolling to the top happen before the animation is the following directive:

yourModule.directive('scrollToTopBeforeAnimation', ['$animate', function ($animate) {
    return {
        restrict: 'A',
        link: function ($scope, element) {
            $animate.on('enter', element, function (element, phase) {

                if (phase === 'start') {

                    window.scrollTo(0, 0);
                }

            })
        }
    };
}]);

I inserted it on my view as follows:

<div scroll-to-top-before-animation>
    <div ng-view class="view-animation"></div>
</div>

Questions:
Answers:

Try this http://ionicframework.com/docs/api/service/$ionicScrollDelegate/

It does scroll to the top of the list scrollTop()