Question
Angular Scroll to Top on Route Change: Router Events and Scroll Position
Question
In an Angular application, when a user scrolls down a long page and clicks a link near the bottom, the route changes correctly, but the next page keeps the previous scroll position instead of starting at the top.
This can make the new page appear empty or incomplete, especially when the next route has only a small amount of content that is visible only after scrolling back to the top.
A possible workaround is calling window.scrollTo(0, 0) inside each component's ngOnInit, but is there a better way to handle this automatically for all routes in the application?
Short Answer
By the end of this page, you will understand how Angular handles scroll position during navigation, how to reset scroll to the top for every route change, and when to use Angular's built-in router scrolling features versus subscribing to router events manually.
Concept
When a route changes in Angular, the framework swaps the displayed component, but the browser window does not always automatically reset its scroll position.
This matters because:
- Users expect a new page view to begin at the top.
- Keeping the old scroll position can make short pages look empty.
- Repeating scroll logic in every component creates duplication.
The main idea is to move this behavior into one central place instead of handling it inside each page component.
In Angular, the most common ways to do this are:
- Use Angular Router's built-in scrolling support with options like
scrollPositionRestoration. - Listen to router navigation events and call
window.scrollTo(0, 0)after successful navigation.
A global solution is better because it:
- keeps page components focused on page-specific logic
- avoids repeated code
- makes navigation behavior consistent across the whole app
For modern Angular apps, the built-in router option is usually the cleanest approach.
Mental Model
Think of routing like turning pages in a book.
- Each route is a new page.
- The browser scroll position is like where your finger is resting on the paper.
- If you turn to a new page but keep your finger halfway down, you do not start reading from the top.
A good routing setup tells the app: whenever a new page opens, place the reader at the top unless there is a special reason not to.
Syntax and Examples
Option 1: Use Angular Router built-in scroll restoration
In many Angular applications, this is the best solution.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
// your routes here
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'top'
})
],
exports: [RouterModule]
})
export class AppRoutingModule {}
What this does
- After navigation, Angular scrolls to the top of the page.
- You configure it once in routing.
- All routes can benefit from the same behavior.
Option 2: Listen to router events manually
If you need custom control, subscribe to router events in a top-level component such as AppComponent.
import { , } ;
{ , } ;
{ filter } ;
({
: ,
:
})
{
() {}
(): {
..
.(( event ))
.( {
.(, );
});
}
}
Step by Step Execution
Consider this example:
this.router.events
.pipe(filter(event => event instanceof NavigationEnd))
.subscribe(() => {
window.scrollTo(0, 0);
});
Here is what happens step by step:
-
this.router.events- Angular provides a stream of router events.
- These events happen during every navigation.
-
.pipe(filter(...))- The code ignores all router events except
NavigationEnd. - This prevents scrolling too early.
- The code ignores all router events except
-
.subscribe(() => { ... })- The callback runs every time navigation finishes.
-
window.scrollTo(0, 0)- The browser window moves to the top-left corner.
- means top position and left position.
Real World Use Cases
This pattern is useful in many real applications:
-
Documentation sites
- A user reads a long article, clicks another page, and should start at the top of the new article.
-
E-commerce apps
- A user scrolls through product listings, then opens a product details page.
- The details page should begin at the top.
-
Admin dashboards
- Users move between reports, settings, and data views.
- Each screen should load from a predictable position.
-
Content-heavy apps
- Blog pages, help centers, and news apps often have long pages.
- Scroll reset avoids confusing empty-looking screens.
-
Mobile web apps
- Limited screen height makes scroll position even more noticeable.
- Starting at the top improves usability.
Real Codebase Usage
In real Angular codebases, developers usually avoid putting window.scrollTo() inside every component.
Common patterns include:
Centralized router configuration
Use router configuration for app-wide behavior:
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'top'
})
This is clean and easy to maintain.
App-level router event handling
If the app needs custom behavior, developers place the logic in AppComponent or a dedicated navigation service.
Examples of custom rules:
- scroll to top only on specific routes
- preserve scroll on tab-like views
- scroll to anchors such as
#section-2 - combine with analytics after navigation
Guarded behavior
Sometimes teams add conditions:
.subscribe(() => {
if (!location.hash) {
window.scrollTo(0, 0);
}
});
This helps when anchor navigation should not be overridden.
Common Mistakes
1. Resetting scroll in every component
This works, but creates duplicated code.
ngOnInit(): void {
window.scrollTo(0, 0);
}
Why it is a problem
- repeated in many files
- easy to forget on new pages
- mixes navigation behavior with component setup
2. Listening to all router events without filtering
Broken approach:
this.router.events.subscribe(() => {
window.scrollTo(0, 0);
});
Problem
Angular emits many events per navigation. This can cause scroll resets at the wrong time.
Better
this.router.events
.pipe(filter(event => event instanceof NavigationEnd))
.( .(, ));
Comparisons
| Approach | Where it lives | Best for | Pros | Cons |
|---|---|---|---|---|
window.scrollTo() in each component | Individual components | Very small apps | Simple to understand | Repetitive and hard to maintain |
Router events + NavigationEnd | AppComponent or service | Custom scrolling rules | Flexible and centralized | More code than built-in config |
scrollPositionRestoration: 'top' | Router config | Most apps | Clean, global, minimal code | Less flexible for special cases |
scrollPositionRestoration vs manual event handling
Cheat Sheet
Quick reference
Built-in Angular router solution
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'top'
})
Manual global solution
this.router.events
.pipe(filter(event => event instanceof NavigationEnd))
.subscribe(() => {
window.scrollTo(0, 0);
});
Smooth scroll version
window.scrollTo({ top: 0, behavior: 'smooth' });
Rules of thumb
- Use a global solution for route-based scrolling.
- Prefer router config when possible.
- Use
NavigationEndfor custom logic.
FAQ
How do I scroll to the top after every Angular route change?
Use Angular router configuration with scrollPositionRestoration: 'top', or subscribe to Router events and call window.scrollTo(0, 0) on NavigationEnd.
What is the best place to put scroll-to-top logic in Angular?
For app-wide behavior, put it in router configuration or a top-level place like AppComponent, not inside every routed component.
Why does Angular keep the old scroll position on navigation?
The route changes the displayed component, but browser scroll behavior is separate. Without explicit handling, the previous scroll position may remain.
Should I use ngOnInit to scroll to the top?
Only if the behavior is specific to one component. For all routes, a centralized solution is better.
Which router event should I use for scrolling?
Use NavigationEnd because it fires after navigation has completed.
Can scroll-to-top break anchor navigation?
Yes. If your app uses URL fragments like #section, always forcing top scroll can interfere with jumping to anchors.
Is window.scrollTo() always safe in Angular?
It is fine in normal browser-only apps, but in server-side rendering environments you should avoid direct usage unless you check that the code runs in the browser.
Mini Project
Description
Build a small Angular app that contains two routes: one long page and one short page. The project demonstrates how route changes can preserve an old scroll position and how to fix that with a centralized scroll-to-top solution.
Goal
Create an Angular app where every route navigation starts at the top of the page without adding scroll logic to each component.
Requirements
- Create at least two routes, one with long content and one with short content.
- Add navigation links between the routes.
- Configure a global scroll-to-top solution for route changes.
- Verify that navigating from the bottom of the long page opens the next page at the top.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.
Angular @ViewChild Returning Undefined: Lifecycle, Child Components, and Fixes
Learn why Angular @ViewChild can be undefined, when it becomes available, and how to access child components correctly using lifecycle hooks.