272

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?

Community
  • 1
  • 1
Matias Gonzalez
  • 2,930
  • 2
  • 15
  • 8

18 Answers18

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>
Konrad Kiss
  • 6,856
  • 1
  • 20
  • 21
  • 1
    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? – xzegga Dec 20 '14 at 20:18
  • 19
    The docs state *If the attribute is set without value, enable scrolling*. So you can shorten the code to just `
    ` and it works just the same (unless you wanted to make scrolling conditional).
    – Daniel Liuzzi Dec 31 '14 at 03:55
  • 7
    it doesn't work for me, the doc doesn't say it will scroll to the top – Pencilcheck Jan 13 '15 at 06:17
  • 6
    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. – axzr Jan 27 '15 at 10:49
  • 1
    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 works – bokkie Feb 05 '15 at 08:29
  • 4
    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 listener – Filip Sobczak Oct 14 '15 at 15:13
  • 1
    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! – Akhoy Dec 07 '15 at 12:56
  • works for me. When editing the element with the browser console thought, I can see it as
    – AlexanderD May 20 '17 at 10:10
  • 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 @axzr 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). – Márton Tamás Apr 03 '19 at 18:21
47

Just put this code to run

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

    window.scrollTo(0, 0);

});
rink.attendant.6
  • 44,500
  • 61
  • 101
  • 156
xmaster
  • 497
  • 5
  • 4
  • 6
    ``$window.scrollTop(0,0)``works better for me than autoscroll. In my case I have views that enter and leave with transitions. – IsidroGH Dec 21 '14 at 15:32
  • 1
    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. – Gregory Bolkenstijn Jun 10 '15 at 11:44
  • 5
    This worked best for me: $rootScope.$on('$stateChangeSuccess',function(){ $("html, body").animate({ scrollTop: 0 }, 200); }); – James Gentes Aug 25 '15 at 15:19
  • $window.scrollTop should be $window.scrollTo, otherwise it says it doesn't exist. This solution works great! – Software Prophets Aug 12 '16 at 11:40
  • @JamesGentes i needed this one too. Thanks. – Mackenzie Browne Nov 26 '17 at 02:47
  • that was smooth thanx – Anand Siddharth Dec 20 '17 at 13:25
  • **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: https://docs.angularjs.org/api/ng/service/$window). – Márton Tamás Apr 03 '19 at 19:02
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 :)

Rizwan Jamal
  • 1,254
  • 10
  • 13
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');
})
wkonkel
  • 311
  • 3
  • 3
  • This works great for my use case. I'm using Angular UI-Router with nested views (several levels deep). Thanks! – Joshua Powell Apr 20 '15 at 23:48
  • 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 active – britztopher Jun 08 '15 at 12:42
  • @britztopher You can hook into the built-in events and maintain your own history, right? – Casey Sep 04 '15 at 20:07
  • 2
    where do you append this `.run` to? the angular your `app` object? – Philll_t Feb 16 '16 at 23:05
  • 1
    @Philll_t: Yes, the `run` function is a method available on every angular module object. – Hinrich May 15 '16 at 01:28
  • This solution turned out to work really well in my scenario. – Hinrich May 15 '16 at 01:29
  • This should be the number 1 answer. Thanks! – mooses Sep 17 '16 at 04:44
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.

James
  • 1,985
  • 18
  • 19
  • 1
    Hmmm.. this scrolls the view to the top *first* and then the view changes on the screen for me. – Strelok Feb 02 '15 at 23:47
  • 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. – mjoyce91 Aug 23 '16 at 20:18
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.

Léon Pelletier
  • 2,701
  • 2
  • 40
  • 67
  • 1
    Great idea, you can make it shorter using: `onEnter: scrollContent` – zucker May 08 '15 at 08:19
  • 2
    What do you put where you wrote `// Your favorite scroll method here` ? – Agent Zebra Jun 17 '15 at 18:37
  • 4
    Good timing: I was two lines above this comment in the original code, trying [ui-router-title](https://github.com/nonplus/angular-ui-router-title). I use `$('html, body').animate({ scrollTop: -10000 }, 100);` – Léon Pelletier Jun 17 '15 at 18:56
  • 1
    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. – Agent Zebra Jun 17 '15 at 19:04
  • You mean the 2nd one has enough content to offer to scroll but doesn't show it? – Léon Pelletier Jun 17 '15 at 19:09
  • 1
    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? – Agent Zebra Jun 17 '15 at 19:14
  • 1
    If you try to switch to javascript instead? `window.scrollTo(0, 0);` – Léon Pelletier Jun 17 '15 at 19:20
  • 1
    Nope, so strange. Doesn't work at all when I switch to JS. – Agent Zebra Jun 17 '15 at 19:27
  • 1
    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. – Léon Pelletier Jun 17 '15 at 19:32
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

Community
  • 1
  • 1
mg1075
  • 17,985
  • 8
  • 59
  • 100
  • 16
    Mine doesn't jump to the bottom, it just retains the scroll position instead of going to the top. – Stephen Smith Apr 06 '14 at 00:58
  • 5
    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. – Will Jun 05 '14 at 01:53
7

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

$rootScope.$on("$stateChangeSuccess", function (event, currentState, previousState) {
    $window.scrollTo(0, 0);
});
Colzak
  • 166
  • 1
  • 7
  • I know this should work, I am extensively using "$stateChangeSuccess", but for some reason $window.scrollTo(0,0) isn't working with that. – Abhas Nov 04 '15 at 12:49
6

you can use this javascript

$anchorScroll()
CodeIsLife
  • 1,205
  • 8
  • 14
  • 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 angularjs – Matias Gonzalez Jan 10 '14 at 23:05
  • 4
    Have you tried using $anchorScroll(), it's documented http://docs.angularjs.org/api/ng.%24anchorScroll. – CodeIsLife Jan 10 '14 at 23:13
  • 1
    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 URL – Roy Apr 21 '14 at 19:00
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);
        });
    });
Vince Verhoeven
  • 1,693
  • 14
  • 25
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 { }
Til
  • 5,150
  • 13
  • 26
  • 34
Farida Anjum
  • 529
  • 6
  • 12
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();
}]);
asp_net
  • 3,567
  • 3
  • 31
  • 58
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.

BuffMcBigHuge
  • 579
  • 5
  • 8
  • 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. – BatteryAcid Oct 19 '17 at 17:30
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.

Dev93
  • 668
  • 1
  • 6
  • 18
  • 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' }) });` – Agent Zebra Jun 17 '15 at 18:56
  • 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 :D – Agent Zebra Jun 17 '15 at 23:26
  • @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! – Dev93 Jun 18 '15 at 03:24
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>
aBertrand
  • 319
  • 2
  • 7
0

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

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

ToughPal
  • 2,231
  • 5
  • 26
  • 30
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!
})
Augie Gardner
  • 2,749
  • 3
  • 25
  • 36
0

This Worked for me including autoscroll

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

Teja
  • 25
  • 9