I am using Angular 5. I have a dashboard where I have few sections with small content and few sections with so large content that I am facing a problem when changing router while going to top. Every time I need to scroll to go to top.
How can I solve this issue so that when I change the router, my view always stay at the top?
There are some solutions, make sure to check them all :)
Option1:
The router outlet will emit the activate
event any time a new component is being instantiated, so we could use (activate)
to scroll (for example) to the top:
app.component.html
<router-outlet (activate)="onActivate($event)"></router-outlet>
app.component.ts
onActivate(event) {
// window.scroll(0,0);
window.scroll({
top: 0,
left: 0,
behavior: 'smooth'
});
//or document.body.scrollTop = 0;
//or document.querySelector('body').scrollTo(0,0)
...
}
As the smooth scroll is not implemented well in Safari, use, for exemple, this solution for a smooth scroll:
onActivate(event) {
let scrollToTop = window.setInterval(() => {
let pos = window.pageYOffset;
if (pos > 0) {
window.scrollTo(0, pos - 20); // how far to scroll on each step
} else {
window.clearInterval(scrollToTop);
}
}, 16);
}
If you wish to be selective, say not every component should trigger the scrolling, you can check it in an if
statement like this:
onActivate(e) {
if (e.constructor.name)==="login"{ // for example
window.scroll(0,0);
}
}
Option2:
Since Angular6.1, we can also use { scrollPositionRestoration: 'enabled' }
on eagerly loaded modules and it will be applied to all routes:
RouterModule.forRoot(appRoutes, { scrollPositionRestoration: 'enabled' })
It will also do the smooth scrolling, already. However this has the inconvenient for doing it on every routing.
Option3:
An other solution is to do the top scrolling on router animation. Add this in every transition where you want to scroll to the top:
query(':enter, :leave', style({ position: 'fixed' }), { optional: true })
If you face this problem in Angular 6, you can fix it by adding the parameter scrollPositionRestoration: 'enabled'
to app-routing.module.ts 's RouterModule:
@NgModule({
imports: [RouterModule.forRoot(routes,{
scrollPositionRestoration: 'enabled'
})],
exports: [RouterModule]
})
scrollPositionRestoration
property does not work with dynamic page content (i.e., where the page content is loaded asynchronously): see this Angular bug report: github.com/angular/angular/issues/24547
EDIT: For Angular 6+, please use Nimesh Nishara Indimagedara's answer mentioning:
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled'
});
Original Answer:
If all fails, then create some empty HTML element (eg: div) at the top (or desired scroll to location) with id="top" on template (or parent template):
<div id="top"></div>
And in component:
ngAfterViewInit() {
// Hack: Scrolls to top of Page after page view initialized
let top = document.getElementById('top');
if (top !== null) {
top.scrollIntoView();
top = null;
}
}
Now there's a built in solution available in Angular 6.1 with scrollPositionRestoration
option.
See my answer on Angular 2 Scroll to top on Route Change.
From Angular Version 6+ No need to use window.scroll(0,0)
For Angular version 6+
from @docs
Represents options to configure the router.
interface ExtraOptions {
enableTracing?: boolean
useHash?: boolean
initialNavigation?: InitialNavigation
errorHandler?: ErrorHandler
preloadingStrategy?: any
onSameUrlNavigation?: 'reload' | 'ignore'
scrollPositionRestoration?: 'disabled' | 'enabled' | 'top'
anchorScrolling?: 'disabled' | 'enabled'
scrollOffset?: [number, number] | (() => [number, number])
paramsInheritanceStrategy?: 'emptyOnly' | 'always'
malformedUriErrorHandler?: (error: URIError, urlSerializer: UrlSerializer, url: string) => UrlTree
urlUpdateStrategy?: 'deferred' | 'eager'
relativeLinkResolution?: 'legacy' | 'corrected'
}
One can use scrollPositionRestoration?: 'disabled' | 'enabled' | 'top'
in
Example:
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled'|'top'
});
And if one requires to manually control the scrolling, No need to use window.scroll(0,0)
Instead from Angular V6 common package has introduced ViewPortScoller
.
abstract class ViewportScroller {
static ngInjectableDef: defineInjectable({ providedIn: 'root', factory: () => new BrowserViewportScroller(inject(DOCUMENT), window) })
abstract setOffset(offset: [number, number] | (() => [number, number])): void
abstract getScrollPosition(): [number, number]
abstract scrollToPosition(position: [number, number]): void
abstract scrollToAnchor(anchor: string): void
abstract setHistoryScrollRestoration(scrollRestoration: 'auto' | 'manual'): void
}
Usage is pretty Straightforward Example:
import { Router } from '@angular/router';
import { ViewportScroller } from '@angular/common'; //import
export class RouteService {
private applicationInitialRoutes: Routes;
constructor(
private router: Router;
private viewPortScroller: ViewportScroller//inject
)
{
this.router.events.pipe(
filter(event => event instanceof NavigationEnd))
.subscribe(() => this.viewPortScroller.scrollToPosition([0, 0]));
}
Although @Vega provides the direct answer to your question, there are issues. It breaks the browser's back/forward button. If you're user clicks the browser back or forward button, they lose their place and gets scrolled way at the top. This can be a bit of a pain for your users if they had to scroll way down to get to a link and decided to click back only to find the scrollbar had been reset to the top.
Here's my solution to the problem.
export class AppComponent implements OnInit {
isPopState = false;
constructor(private router: Router, private locStrat: LocationStrategy) { }
ngOnInit(): void {
this.locStrat.onPopState(() => {
this.isPopState = true;
});
this.router.events.subscribe(event => {
// Scroll to top if accessing a page, not via browser history stack
if (event instanceof NavigationEnd && !this.isPopState) {
window.scrollTo(0, 0);
this.isPopState = false;
}
// Ensures that isPopState is reset
if (event instanceof NavigationEnd) {
this.isPopState = false;
}
});
}
}
In my case I just added
window.scroll(0,0);
in ngOnInit()
and its working fine.
Angular 6.1 and later:
You can use built in solution available in Angular 6.1+ with option scrollPositionRestoration: 'enabled'
to achieve the same.
@NgModule({
imports: [RouterModule.forRoot(routes,{
scrollPositionRestoration: 'enabled'
})],
exports: [RouterModule]
})
Angular 6.0 and earlier:
import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
import { Location, PopStateEvent } from "@angular/common";
@Component({
selector: 'my-app',
template: '<ng-content></ng-content>',
})
export class MyAppComponent implements OnInit {
private lastPoppedUrl: string;
private yScrollStack: number[] = [];
constructor(private router: Router, private location: Location) { }
ngOnInit() {
this.location.subscribe((ev:PopStateEvent) => {
this.lastPoppedUrl = ev.url;
});
this.router.events.subscribe((ev:any) => {
if (ev instanceof NavigationStart) {
if (ev.url != this.lastPoppedUrl)
this.yScrollStack.push(window.scrollY);
} else if (ev instanceof NavigationEnd) {
if (ev.url == this.lastPoppedUrl) {
this.lastPoppedUrl = undefined;
window.scrollTo(0, this.yScrollStack.pop());
} else
window.scrollTo(0, 0);
}
});
}
}
Note: The expected behavior is that when you navigate back to the page, it should remain scrolled down to the same location it was when you clicked on the link, but scrolling to the top when arriving at every page.
if your using mat-sidenav give an id to the router outlet( if you have a parent and child router outlets) and use activate function in it <router-outlet id="main-content" (activate)="onActivate($event)">
and use this 'mat-sidenav-content' query selector to scroll top onActivate(event) { document.querySelector("mat-sidenav-content").scrollTo(0, 0); }
id
(I have a single router-outlet
on my app). I also did it on a more 'angular way': @ViewChild(MatSidenavContainer) sidenavContainer: MatSidenavContainer; onActivate() { this.sidenavContainer.scrollable.scrollTo({ left: 0, top: 0 }); }
For some one who is looking for scroll function just add the function and call when ever needed
scrollbarTop(){
window.scroll(0,0);
}
I keep looking for a built in solution to this problem like there is in AngularJS. But until then this solution works for me, It's simple, and preserves back button functionality.
app.component.html
<router-outlet (deactivate)="onDeactivate()"></router-outlet>
app.component.ts
onDeactivate() {
document.body.scrollTop = 0;
// Alternatively, you can scroll to top by using this other call:
// window.scrollTo(0, 0)
}
Answer from zurfyx original post
You just need to create a function which contains adjustment of scrolling of your screen
for example
window.scroll(0,0) OR window.scrollTo() by passing appropriate parameter.
window.scrollTo(xpos, ypos) --> expected parameter.
None of the above worked for me for some reason :/, so I added an element ref to a top element in app.component.html
, and (activate)=onNavigate($event)
to the router-outlet
.
<!--app.component.html-->
<div #topScrollAnchor></div>
<app-navbar></app-navbar>
<router-outlet (activate)="onNavigate($event)"></router-outlet>
Then I added the child to the app.component.ts file to the type of ElementRef
, and had it scroll to it on activation of the router-outlet.
export class AppComponent {
@ViewChild('topScrollAnchor') topScroll: ElementRef;
onNavigate(event): any {
this.topScroll.nativeElement.scrollIntoView({ behavior: 'smooth' });
}
}
Here's the code in stackblitz
The solution that worked for me:
document.getElementsByClassName('layout-content')[0].scrollTo(0, 0);
It's worked in angular 8, 9 and 10.
scrollTo(0, 0)
did'nt work for me, but scrollIntoView()
did. Thanks anyway!
Just add this line on app.module.ts
file :
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled' //scroll to the top
})
I'm using Angular 11.1.4 and its worked for me
just add
window.scrollTo({ top: 0 });
to ngOnInit()
Here is a solution that only scrolls to top of Component if first time visiting for EACH component (in case you need to do something different per component):
In each Component:
export class MyComponent implements OnInit {
firstLoad: boolean = true;
...
ngOnInit() {
if(this.firstLoad) {
window.scroll(0,0);
this.firstLoad = false;
}
...
}
Try this:
app.component.ts
import {Component, OnInit, OnDestroy} from '@angular/core';
import {Router, NavigationEnd} from '@angular/router';
import {filter} from 'rxjs/operators';
import {Subscription} from 'rxjs';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit, OnDestroy {
subscription: Subscription;
constructor(private router: Router) {
}
ngOnInit() {
this.subscription = this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe(() => window.scrollTo(0, 0));
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
export class AppComponent { constructor(private router: Router) { router.events.subscribe((val) => { if (val instanceof NavigationEnd) { window.scrollTo(0, 0); } }); } }
Component: Subscribe to all routing events instead of creating an action in the template and scroll on NavigationEnd b/c otherwise you'll fire this off on bad navs or blocked routes, etc... This is a sure fire way to know that if a route successfully is navigated to, then sooth scroll. Otherwise, do nothing.
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy {
router$: Subscription;
constructor(private router: Router) {}
ngOnInit() {
this.router$ = this.router.events.subscribe(next => this.onRouteUpdated(next));
}
ngOnDestroy() {
if (this.router$ != null) {
this.router$.unsubscribe();
}
}
private onRouteUpdated(event: any): void {
if (event instanceof NavigationEnd) {
this.smoothScrollTop();
}
}
private smoothScrollTop(): void {
const scrollToTop = window.setInterval(() => {
const pos: number = window.pageYOffset;
if (pos > 0) {
window.scrollTo(0, pos - 20); // how far to scroll on each step
} else {
window.clearInterval(scrollToTop);
}
}, 16);
}
}
HTML
<router-outlet></router-outlet>
try this
@NgModule({
imports: [RouterModule.forRoot(routes,{
scrollPositionRestoration: 'top'
})],
exports: [RouterModule]
})
this code supported angular 6<=
Just Add
ngAfterViewInit() {
window.scroll(0,0)
}
Success story sharing
window
object are not working in angular 5. Any guesses why?scroll
events are blacklisted by ngzones.