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

18

273

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?

Evangelize answered 10/1, 2014 at 22:53 Comment(0)
A
582

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>
Andel answered 3/7, 2014 at 8:53 Comment(9)
This work for me, the page scroll fine to top, but I am using scrollTo with smoothScroll directive, are there way to do a smoth scroll to top?, additionally your solution scroll to top of view not to the page are there to scroll to top of the page?Kuehnel
The docs state If the attribute is set without value, enable scrolling. So you can shorten the code to just <div class="ng-view" autoscroll></div> and it works just the same (unless you wanted to make scrolling conditional).Tieck
it doesn't work for me, the doc doesn't say it will scroll to the topCream
I find that if autoscroll is set to true then when a user 'goes back' they return to the top of the page, not where they left it, which is undesired behaviour.Droop
for me it doesn't work with this css: html, body { font-size: 14px; font-family: 'Open Sans', sans-serif; overflow: auto; height: 100%; }, but if I take out the hieght: 100% it worksProbst
this will scroll your view to this div. So it will scroll to top of the page only if it happens to be at the top of the page. Otherwise you'll have to run $window.scrollTo(0,0) in $stateChangeSuccess event listenerGarlic
This doesn't work if there's no scrollbar on the page. I had set overflow:hidden on the page. So I had to explicitly scroll to the top of the div by using $('div').scrollTop(0). But +1 for the reason!Capitulate
<div ng-view autoscroll> works for me. When editing the element with the browser console thought, I can see it as <div ng-view="" autoscroll="">Eppes
Although this is a quick and easy fix for scrolling to top on new views, it scrolls to top when the user navigates through history too, as @Droop mentioned. It's an important damage to the user experience, just like the default behavior of AngularJS routing (not scrolling to top on new views).Demise
W
47

Just put this code to run

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

    window.scrollTo(0, 0);

});
Wintertide answered 15/10, 2014 at 19:52 Comment(7)
$window.scrollTop(0,0)works better for me than autoscroll. In my case I have views that enter and leave with transitions.Gwenni
This also works better for me than autoscroll="true". Autoscroll was too late for some reason, it would scroll to the top after the new view was already visible.Psalms
This worked best for me: $rootScope.$on('$stateChangeSuccess',function(){ $("html, body").animate({ scrollTop: 0 }, 200); });Scurf
$window.scrollTop should be $window.scrollTo, otherwise it says it doesn't exist. This solution works great!Cherriecherrita
@JamesGentes i needed this one too. Thanks.Suspensoid
that was smooth thanxAlegar
1. Though it solves the literal question (scroll to top), I guess we all want the app scrolling to the fragment as well, in case the location contains one (eg. ending like #foo). $anchorScroll() solves this task, while $window.scrollTop(0, 0) always scrolls to top. 2. Using $window instead of window in AngularJS would be appropriate (see docs: docs.angularjs.org/api/ng/service/$window).Demise
B
36

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 :)

Bonkers answered 24/6, 2016 at 7:38 Comment(1)
Finally, a solution that is working with sticky-headers! Thank you!Lavernlaverna
C
31

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');
})
Castaway answered 19/3, 2015 at 19:48 Comment(7)
This works great for my use case. I'm using Angular UI-Router with nested views (several levels deep). Thanks!Fulcrum
FYI - you need to be in HTML5 mode in angular to use pushState: $locationProvider.html5Mode(true); @wkronkel is there a way to accomplish this when not using HTML5 mode in angular? Cause page reload are throwing 404 errors due to html 5 mode being activeOlivenite
@Olivenite You can hook into the built-in events and maintain your own history, right?Evolutionary
where do you append this .run to? the angular your app object?Bromate
@Philll_t: Yes, the run function is a method available on every angular module object.Dhyana
This solution turned out to work really well in my scenario.Dhyana
This should be the number 1 answer. Thanks!Hum
A
18

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.

Aenneea answered 13/12, 2014 at 22:51 Comment(2)
Hmmm.. this scrolls the view to the top first and then the view changes on the screen for me.Easing
Clean solution I was looking for. If you click "back" it takes you to where you left off - this might not be desirable for some, but I like it in my case.Exit
T
15

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.

Tannate answered 27/2, 2015 at 16:56 Comment(9)
Great idea, you can make it shorter using: onEnter: scrollContentAnimation
What do you put where you wrote // Your favorite scroll method here ?Vow
Good timing: I was two lines above this comment in the original code, trying ui-router-title. I use $('html, body').animate({ scrollTop: -10000 }, 100);Darbydarce
Haha, great! Strange tho, it does not work for me all the time. I have some views that are so short they don't have a scroll, and two views that have a scroll, when I place onEnter: scrollContent on each view it only works on the first view/ route, not the 2nd one. Strange.Vow
You mean the 2nd one has enough content to offer to scroll but doesn't show it?Darbydarce
I'm not sure, no, It just doesn't scroll to top when it should. When I click between the 'routes' some of them are still scrolling to where the ng-view is, rather than the absolute top of the enclosing page. Does that make sense?Vow
If you try to switch to javascript instead? window.scrollTo(0, 0);Darbydarce
Nope, so strange. Doesn't work at all when I switch to JS.Vow
Check that there is not a sub-element that is hijacking the body scroll. Anyway, that should be simple if you play a bit in your browser developer panel with the DOM and the console.Darbydarce
V
13

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

Vengeful answered 14/2, 2014 at 19:54 Comment(2)
Mine doesn't jump to the bottom, it just retains the scroll position instead of going to the top.Newtonnext
This doesn't answer the question. I've got the same problem - when the route changes the scroll level just stays in the same place instead of scrolling to the top. I can get it to scroll to the top, but only by changing the hash, which I don't want to do for obvious reasons.Charleencharlemagne
F
7

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

$rootScope.$on("$stateChangeSuccess", function (event, currentState, previousState) {
    $window.scrollTo(0, 0);
});
Frum answered 19/6, 2015 at 9:40 Comment(1)
I know this should work, I am extensively using "$stateChangeSuccess", but for some reason $window.scrollTo(0,0) isn't working with that.Clew
S
6

you can use this javascript

$anchorScroll()
Simpleton answered 10/1, 2014 at 22:58 Comment(3)
Well, it's not that simple in angularjs. Your solution is only valid in jquery. What I'm looking for is an elegant way to do this in angularjsEvangelize
Have you tried using $anchorScroll(), it's documented docs.angularjs.org/api/ng.%24anchorScroll.Simpleton
FYI If you specify a location with $location.hash('top') before $anchorScroll() the default forward/back browser buttons no longer work; they keep redirecting you to the hash in the URLHertzfeld
R
4

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);
        });
    });
Rhymester answered 6/8, 2015 at 20:57 Comment(0)
E
4

Simple Solution, add scrollPositionRestoration in the main route module enabled.
Like this:

const routes: Routes = [

 {
   path: 'registration',
   loadChildren: () => RegistrationModule
},
];

 @NgModule({
  imports: [
   RouterModule.forRoot(routes,{scrollPositionRestoration:'enabled'})
  ],
 exports: [
 RouterModule
 ]
 })
  export class AppRoutingModule { }
Ephram answered 29/1, 2019 at 9:47 Comment(0)
M
3

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();
}]);
Mckinnon answered 31/10, 2014 at 16:57 Comment(0)
J
3

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.

Jacal answered 11/5, 2016 at 16:53 Comment(1)
This is the only one that worked with angular-material for me, also placing the id="top-nav" on the correct element was important.Anatase
S
2

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.

Swanhildas answered 8/2, 2015 at 23:51 Comment(3)
How do you implement this? Where would you put it in regular angular-ui routes code such as the following? var routerApp = angular.module('routerApp', ['ui.router']); routerApp.config(function($stateProvider, $urlRouterProvider, $locationProvider) { $urlRouterProvider.otherwise('/home'); $locationProvider.html5Mode(false).hashPrefix(""); $stateProvider // HOME STATES AND NESTED VIEWS ======================================== .state('home', { url: '/home', templateUrl: 'partial-home.html' }) });Vow
hmmm... didn't work :-( It just kills the app. .state('web', { url: '/webdev', templateUrl: 'partial-web.html', controller: function($scope) { $scope.clients = ['client1', 'client2', 'client3']; // I put it here } }) I'm just learning this stuff, maybe I'm missing something :DVow
@AgentZebra At a minimum, the controller is going to need to take $timeout as an input (since my code references it), but perhaps more importantly, the controller I'm mentioning needs to live at a level above an individual state (you could include the statements in your top-level controller). The initial AngularJS learning curve is indeed very difficult; good luck!Swanhildas
F
2

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>
Fumigant answered 15/7, 2016 at 5:9 Comment(0)
P
0

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

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

Pencil answered 17/7, 2014 at 8:59 Comment(0)
G
0

I have finally gotten what I needed.

I needed to scroll to the top, but wanted some transitions not to

You can control this on a route-by-route level.
I'm combining the above solution by @wkonkel and adding a simple noScroll: true parameter to some route declarations. Then I'm catching that in the transition.

All in all: This floats to the top of the page on new transitions, it doesn't float to the top on Forward / Back transitions, and it allows you to override this behavior if necessary.

The code: (previous solution plus an extra noScroll option)

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

Put that in your app.run block and inject $state... myApp.run(function($state){...})

Then, If you don't want to scroll to the top of the page, create a route like this:

.state('someState', {
  parent: 'someParent',
  url: 'someUrl',
  noScroll : true // Notice this parameter here!
})
Girlish answered 28/11, 2017 at 19:6 Comment(0)
B
0

This Worked for me including autoscroll

<div class="ngView" autoscroll="true" >

Bausch answered 1/9, 2018 at 16:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.