Reputation: 567
Previous Behavior:
Changing the route or navigation path would not affect the scroll position when navigating to another route. I.e the contents can change without the scroll position changing.
Current Behavior:
Changing the route will put you right back to the top of the page.
Action Done So Far:
Tested on current and a fresh new Angular 6 project
Is this a bug? change in feature? or is there a parameter I am missing.
Upvotes: 16
Views: 35974
Reputation: 650
This answer is for those who came here because they want the scroll position not to change when a route navigation is used to update the query params like so:
this.router.navigate([], {
relativeTo: this.activatedRoute,
queryParams,
});
Enabling scrollPositionRestoration
is recommended by Angular:
'enabled'- Restores the previous scroll position on backward navigation, else sets the position to the anchor if one is provided, or sets the scroll position to [0, 0] (forward navigation). This option will be the default in the future.
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled',
})
But when the route is changed, Angular considers a simple query param change to be a "forward navigation", even if the route itself stays the same. This workaround uses the pairwise function to compare the current and previous router events and see if the route actually changed, or only the route parameters (i.e. query params). NOTE: If this feature request gets implemented, this workaround will no longer be needed.
export class AppModule {
constructor(
private router: Router,
private viewportScroller: ViewportScroller,
) {
this.viewportScroller.setHistoryScrollRestoration('manual'); // Disable automatic scroll restoration to avoid race conditions
this.handleScrollOnNavigation();
}
private handleScrollOnNavigation(): void {
this.router.events.pipe(
// import { Event } from '@angular/router'
filter((e: Event): e is Scroll => e instanceof Scroll),
pairwise(),
).subscribe((e: Scroll[]) => {
const previous = e[0];
const current = e[1];
if (current.position) {
// Backward navigation
this.viewportScroller.scrollToPosition(current.position);
} else if (current.anchor) {
// Anchor navigation
this.viewportScroller.scrollToAnchor(current.anchor);
} else {
// Check if routes match, or if it is only a query param change
if (this.getBaseRoute(previous.routerEvent.urlAfterRedirects) !== this.getBaseRoute(current.routerEvent.urlAfterRedirects)) {
// Routes don't match, this is actual forward navigation
// Default behavior: scroll to top
this.viewportScroller.scrollToPosition([0, 0]);
}
}
});
}
private getBaseRoute(url: string): string {
// return url without query params
return url.split('?')[0];
}
}
Upvotes: 2
Reputation: 21
If someone still have problems with that there is a solution.
@Injectable()
export class AdsService {
private _position = new BehaviorSubject<number>(0);
constructor() {}
get position$(): Observable<number> {
return this._position.asObservable();
}
setPosition(position: number): void {
this._position.next(position);
}
}
export class MyComponent implements OnInit, OnDestroy, AfterViewInit {
@ViewChild('contentArea') private contentArea: ElementRef<HTMLDivElement>;
constructor(private adsService: AdsService) {}
ngAfterViewInit(): void {
this.adsService.position$
.pipe(
filter((p) => p !== 0),
take(1)
)
.subscribe((p) => {
this.contentArea.nativeElement.scrollTop = p;
});
}
ngOnDestroy(): void {}
ngOnInit(): void {}
savePosition = () => {
// this.router.navigate([item.id]);
const scrollTop = this.contentArea.nativeElement.scrollTop;
this.adsService.setPosition(scrollTop);
};
}
<div #contentArea class="overflow">
<div
*ngFor="let item of activeAds; trackBy: trackByFn"
[routerLink]="item.id"
(click)="savePosition()"
>
{{item.name}}
</div>
</div>
Upvotes: 0
Reputation: 1
if scroll restoration doesnt work , Create a service that updates the current scroll Position on the change of that route and assign the scroll position to that service scroll position on ngOnit for the new route component and reset the service scroll.
Upvotes: 0
Reputation: 1
if it doesnt work with ScrollPositionRestoration:'enabled' configuration use the [scrollTop] property binding for the container and assign the value
Upvotes: 0
Reputation: 567
Seems like setting 'scrollPositionRestoration' to disabled fixes it
RouterModule.forRoot(
appRoutes,
{ scrollPositionRestoration: 'disabled' } // <-- HERE
)
See https://angular.io/api/router/ExtraOptions
Upvotes: 26
Reputation: 3162
The scroll position won't change after the route is changed. This is always the default behaviour for Angular.
However, lots of devs are manually doing a window.scroll(0, 0)
to overwrite this behaviour.
I would suggest you check if something in your code is doing this. Because it might be a newly installed 3rd party library or another developer's code commit.
Also, according to the following official article:
Angular v6.1 Now Available — TypeScript 2.9, Scroll Positioning, and more
There is a new option to keep the original scroll position by using
RouterModule.forRoot(routes, {scrollPositionRestoration: 'enabled'})
I believe this is not directly related to the question you are asking but just something good to know.
Upvotes: 7