How to enable Anchor Scrolling of Angular Router and their problems

Quân Đỗ
4 min readMay 27, 2020

--

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!

--

--

Quân Đỗ
Quân Đỗ

Written by Quân Đỗ

Share with Everyone my solutions. Full Stack JS Developer / Technical Leader.

Responses (2)