Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Oguz Demirkapi
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Oguz Demirkapi@demirkapi )

Enabling The Second-Click Of A RouterLink Fragment Using onSameUrlNavigation Reload In Angular 7.1.3

By Ben Nadel on

SPECIAL THANKS: The ideas contained in this post are from Vasiliy Mazhekin. I am only codifying them here for my own reference and mental model.

By default, when you enable anchor / fragment scrolling in your Angular 7.1.3 application, the fragment link works on the first click; but, upon clicking the same fragment link for the second time, the Router does nothing. This does not mirror the expected behavior of the Browser; and, a couple of weeks ago, I published a "polyfill" that enabled the second-click of a fragment-based RouterLink. In the comments to that post, Vasiliy Mazhekin pointed out that you don't actually need a polyfill - if you enable the "onSameUrlNavigation" option in the Router configuration, second-clicks of a fragment-based RouterLink will work as the user would hope. I had never used the "onSameUrlNavigation" option before; so, I wanted to provide an updated fragment demo in order to showcase the approach.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

When you look at the Angular documentation, it's unclear [to me] what the "onSameUrlNavigation" option actually does. If you want to know more about it, Simon McClive has an in-depth article on how you might use "onSameUrlNavigation" to reload data. But, for the purposes of my exploration, the important thing to understand is that the "onSameUrlNavigation" option really does nothing more than re-trigger the series of Navigation Events. This is important because the RouterScroller listens for the NavigationEnd event in order to trigger the Scroll event which is what eventually scrolls the viewport to the fragment target.

To see this configuration in action, let's import the RouterModule with "anchorScrolling" enabled and the "onSameUrlNavigation" option set to "reload":

  • // Import the core angular services.
  • import { BrowserModule } from "@angular/platform-browser";
  • import { NgModule } from "@angular/core";
  • import { RouterModule } from "@angular/router";
  •  
  • // Import the application components and services.
  • import { AppComponent } from "./app.component";
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • @NgModule({
  • imports: [
  • BrowserModule,
  • RouterModule.forRoot(
  • [],
  • {
  • // Tell the router to use the hash instead of HTML5 pushstate.
  • useHash: true,
  •  
  • // In order to get anchor / fragment scrolling to work at all, we need to
  • // enable it on the router.
  • anchorScrolling: "enabled",
  •  
  • // Once the above is enabled, the fragment link will only work on the
  • // first click. This is because, by default, the Router ignores requests
  • // to navigate to the SAME URL that is currently rendered. Unfortunately,
  • // the fragment scrolling is powered by Navigation Events. As such, we
  • // have to tell the Router to re-trigger the Navigation Events even if we
  • // are navigating to the same URL.
  • onSameUrlNavigation: "reload",
  •  
  • // Let's enable tracing so that we can see the aforementioned Navigation
  • // Events when the fragment is clicked.
  • enableTracing: true,
  • scrollPositionRestoration: "enabled"
  • }
  • )
  • ],
  • declarations: [
  • AppComponent
  • ],
  • providers: [
  • // CAUTION: We don't need to specify the LocationStrategy because we are setting
  • // the "useHash" property in the Router module above (which will be setting the
  • // strategy for us).
  • // --
  • // {
  • // provide: LocationStrategy,
  • // useClass: HashLocationStrategy
  • // }
  • ],
  • bootstrap: [
  • AppComponent
  • ]
  • })
  • export class AppModule {
  • // ...
  • }

Now, let's setup an App Component that uses fragment-based RouterLinks:

  • // Import the core angular services.
  • import _ = require( "lodash" );
  • import { Component } from "@angular/core";
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • @Component({
  • selector: "my-app",
  • styleUrls: [ "./app.component.less" ],
  • template:
  • `
  • <p id="top-of-page" class="nav">
  • <a routerLink="." fragment="section-a">Section A</a>
  • <a routerLink="." fragment="section-b">Section B</a>
  • </p>
  •  
  • <section id="section-a">
  •  
  • <h2>
  • Section A
  • </h2>
  •  
  • <p *ngFor="let i of range">
  • Filler content goes here...
  • </p>
  •  
  • </section>
  •  
  • <p>
  • <a routerLink="." fragment="top-of-page">Back to Top</a>
  • </p>
  •  
  • <section id="section-b">
  •  
  • <h2>
  • Section B
  • </h2>
  •  
  • <p *ngFor="let i of range">
  • Filler content goes here...
  • </p>
  •  
  • </section>
  •  
  • <p>
  • <a routerLink="." fragment="top-of-page">Back to Top</a>
  • </p>
  • `
  • })
  • export class AppComponent {
  •  
  • public range: number[] = _.range( 1, 30 );
  •  
  • }

Now, if we run this Angular application in the browser and click on the fragment link for "Section A" twice in a row, we can see that the Router triggers the set of Navigation Events twice; and, upon the second click of the fragment link, successfully scrolls to the target element:


 
 
 

 
 Enabling RouterLink fragment behavior using the OnSameUrlNavigation confirugation in Angular 7.1.3. 
 
 
 

As you can see, with our RouterModule configuration, each click of the fragment-based RouterLink precipitated a full cycle of Navigation Events. The RouterScroller service listens for the NavigationStart and NavigationEnd events specifically and emits a Scroll event. This Scroll event is then parled into a call to the ViewportScroller service, which is what gets the browser to scroll down to the fragment's target element.

Again, a huge thanks to Vasiliy Mazhekin who pointed this out! It's great that Angular supports this behavior out of the box without having to polyfill it.



Reader Comments

Hi Ben,

Thanks for the info.

I have one more challenge in this approach, is it possible to Make smooth transition when we jump to fragment section? like slight animation kind of slide instead of sudden appearance of the section.
Like the SPA app in plain html template, Thanks in advance

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.