How to enable Anchor Scrolling of Angular Router and their problems
Nowadays, Angular allows us to custom more function of their Router Module. However, something is not perfect, and anchor scrolling is a particular example.
This article will show you what problem occurs when implement Anchor Scrolling with standard way, and, of course, I will give you a solution.
First, Let’s see how to do in standard way and their problem.
Standard Way
We will use the params options of Router Module to implement, which will be call ExtraOptions
Router ExtraOptions A set of configuration options for a router module, provided in the forRoot()
method.
ExtraOptions of Angular Router Module have many params. However, in this article, we just have to care with 3 params related to anchor scrolling. (scrollPositionRestoration, anchorScrolling, scrollOffset)
scrollPositionRestoration
Router Scroller The first function is to store and restore the scroll position. Every time Router navigates, it stores the scroll position at that point. And when the screen returns to the previous screen by the return operation of the browser, it automatically restores to the stored scroll position.
Since the timing is controlled by the Router in this restoration process, the scroll moves after the routing process of the previous screen is finished.
anchorScrolling
Another params is to scroll to an element with a corresponding ID if there is a fragment like #foo
in the URL. This is also a function commonly used in static HTML pages, but in Angular it did not work because the browser searches for the element earlier than component generation by Router.
This time Router has the Anchor Scrolling function, so even with Angular application it is possible to scroll by #foo
. In addition to navigation by Router, it scrolls in the same way even if you reload.
Notes: the fragment need to be same with the id attribute of HTML tag.
scrollOffset
Configures the scroll offset the router will use when scrolling to an element.
When given a tuple with x and y position value, the router uses that offset each time it scrolls. When given a function, the router invokes the function every time it restores scroll position.
Assumed you have a fixed menu with about 60px, the anchor scrolling will be behind the menu when finish scrolled.
If you want to shift the scroll position, such as when the header is fixed or sticky, set scrollOffset
with the option of the second argument ofRouterModule.forRoot
method.
The x coordinate will be 0, and the y coordinate is shifted down by about 65 px (I want to the HTML tag far from header 5px)
Final Standard Settings
@NgModule({
imports: [
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled',
scrollOffset: [0, 64] // [x, y]
})
],
exports: [RouterModule]
})
export class AppRoutingModule {}
It will work perfectly.
So what is the problem?
You can see my example below.
Transition
By default, that transition between is an abrupt jump.
That sort of a jump can be jarring. That’s where scroll-behavior
css comes in and allows us to set a smooth scrolling transition. This will give us the ability to do that natively in CSS, once browser support improves.
.module {
scroll-behavior: smooth;
}
Notes: Browser supported
Wrong Position
Unfortunately, after added this css and navigate to another URL with fragment, the scroller will scroll to wrong position.
Check out the code in Stackblitz: https://stackblitz.com/edit/standard-anchor-scrolling
If right position, no transition. If have transition, wrong position.
It would be even worse when we get async data from API, the anchor scrolling was not working.
So what is the solution?
To solve this problem, I will manually config the scroller for Router Module.
First of all, I need to command all related extraOptions.
@NgModule({
imports: [RouterModule.forRoot(routes, {
/* instead of use extraOptions for Router */
// scrollPositionRestoration: 'enabled',
// anchorScrolling: 'enabled',
// scrollOffset: [0, 50],
})],
exports: [RouterModule]
})
Secondly, add the below code into App Module. It will help you handle manually each time Scroll Event occurs. The ViewportScroller provides us many functions to scroll. Because of viewportScroller.scrollToAnchor was triggered before the UI mounted, It will getBoundingClientRect() wrong value, this leads Anchor Scrolling go to wrong position.
export class AppModule {
constructor(router: Router, viewportScroller: ViewportScroller) {
viewportScroller.setOffset([0, 50]);
router.events.pipe(filter(e => e instanceof Scroll)).subscribe((e: Scroll) => {
if (e.anchor) {
// anchor navigation
/* setTimeout is the core line to solve the solution */
setTimeout(() => {
viewportScroller.scrollToAnchor(e.anchor);
})
} else if (e.position) {
// backward navigation
viewportScroller.scrollToPosition(e.position);
} else {
// forward navigation
viewportScroller.scrollToPosition([0, 0]);
}
});
}
}
Async Data
Mix with Resolver for handling the async data call from api. You can see how to implement Resolver in the below example.
Check all out the code here: https://stackblitz.com/edit/solution-anchor-scrolling
Thank for Reading!