Question

Angular Scroll to Top on Route Change: Router Events and Scroll Position

typescriptangularangular2-routingangular2-templateangular2-directives

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.

Take Quiz

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 { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  constructor(private router: Router) {}

  ngOnInit(): void {
    this.router.events
      .pipe(filter(event => event instanceof NavigationEnd))
      .subscribe(() => {
        window.scrollTo(0, 0);
      });
  }
}

Why NavigationEnd?

Angular emits many router events during navigation. NavigationEnd means the route change has completed, so it is a safe time to reset scroll.

Example with smooth scrolling

window.scrollTo({ top: 0, behavior: 'smooth' });

Use this only if smooth scrolling matches your app's UX.

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:

  1. this.router.events

    • Angular provides a stream of router events.
    • These events happen during every navigation.
  2. .pipe(filter(...))

    • The code ignores all router events except NavigationEnd.
    • This prevents scrolling too early.
  3. .subscribe(() => { ... })

    • The callback runs every time navigation finishes.
  4. window.scrollTo(0, 0)

    • The browser window moves to the top-left corner.
    • 0, 0 means top position and left position.

Small navigation trace

Suppose a user is on /products and has scrolled to Y = 1800.

Then the user clicks a link to /about.

Angular emits events like:

  • NavigationStart
  • route matching events
  • activation events
  • NavigationEnd

When NavigationEnd occurs, your subscription runs:

window.scrollTo(0, 0);

Now the /about page starts from the top.

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.
Take Quiz

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.

Reusable navigation service

Larger apps may wrap this logic in a service so routing behavior stays separate from UI components.

That keeps the codebase easier to test and reason about.

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))
  .subscribe(() => window.scrollTo(0, 0));

3. Using the wrong lifecycle hook for global behavior

Putting the logic in a page component lifecycle hook only affects that component.

If you want app-wide behavior, place it in:

  • router configuration
  • AppComponent
  • a global navigation service

4. Ignoring anchor links

If your app uses URLs like /docs#install, always forcing scroll to top may break expected anchor behavior.

5. Forgetting server-side rendering concerns

Direct use of window can fail in environments where window does not exist.

If your app uses SSR, prefer Angular router options or platform checks before calling window directly.

Comparisons

ApproachWhere it livesBest forProsCons
window.scrollTo() in each componentIndividual componentsVery small appsSimple to understandRepetitive and hard to maintain
Router events + NavigationEndAppComponent or serviceCustom scrolling rulesFlexible and centralizedMore code than built-in config
scrollPositionRestoration: 'top'Router configMost appsClean, global, minimal codeLess flexible for special cases

scrollPositionRestoration vs manual event handling

FeatureBuilt-in router optionManual subscription
Easy setupYesNo
Works globallyYesYes
Custom conditionsLimitedStrong
Direct controlLowHigh
BoilerplateMinimalMore

NavigationEnd vs ngOnInit

OptionPurposeScope
NavigationEndReact to completed route changesGlobal navigation behavior
ngOnInitInitialize one componentPer-component behavior
Take Quiz

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 NavigationEnd for custom logic.
  • Avoid repeating scroll logic in every component.
  • Be careful with anchor links and SSR.

Common event to use

  • NavigationEnd: best event for post-navigation scroll logic

Common pitfall

this.router.events.subscribe(() => {
  window.scrollTo(0, 0);
});

This is too broad. Filter to NavigationEnd.

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 window usage unless you check that the code runs in the browser.

Take Quiz

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

  1. Create at least two routes, one with long content and one with short content.
  2. Add navigation links between the routes.
  3. Configure a global scroll-to-top solution for route changes.
  4. Verify that navigating from the bottom of the long page opens the next page at the top.
Take Quiz

Keep learning

Related questions

View more