[routing] Angular2 Routing with Hashtag to page anchor

I wish to add some links on my Angular2 page, that when click, will jump to specific positions within that page, like what normal hashtags do. So the links would be something like

/users/123#userInfo
/users/123#userPhoto
/users/123#userLikes

etc.

I don't think I need HashLocationStrategy, as I'm fine with the normal Angular2 way, but if I add directly, the link would actually jump to the root, not somewhere on the same page. Any direction is appreciated, thanks.

This question is related to routing angular hashtag

The answer is


Sorry for answering it bit late; There is a pre-defined function in the Angular Routing Documentation which helps us in routing with a hashtag to page anchor i.e, anchorScrolling: 'enabled'

Step-1:- First Import the RouterModule in the app.module.ts file:-

imports:[ 
    BrowserModule, 
    FormsModule,
    RouterModule.forRoot(routes,{
      anchorScrolling: 'enabled'
    })
  ],

Step-2:- Go to the HTML Page, Create the navigation and add two important attributes like [routerLink] and fragment for matching the respective Div ID's:-

<ul>
    <li> <a [routerLink] = "['/']"  fragment="home"> Home </a></li>
    <li> <a [routerLink] = "['/']"  fragment="about"> About Us </a></li>
  <li> <a [routerLink] = "['/']"  fragment="contact"> Contact Us </a></li>
</ul>

Step-3:- Create a section/div by matching the ID name with the fragment:-

<section id="home" class="home-section">
      <h2>  HOME SECTION </h2>
</section>

<section id="about" class="about-section">
        <h2>  ABOUT US SECTION </h2>
</section>

<section id="contact" class="contact-section">
        <h2>  CONTACT US SECTION </h2>
</section>

For your reference, I have added the example below by creating a small demo which helps to solve your problem.

Demo : https://routing-hashtag-page-anchors.stackblitz.io/


None of the previous answers worked for me. In a last ditch effort, I tried in my template:

<a (click)="onClick()">From Here</a>
<div id='foobar'>To Here</div>

With this in my .ts:

onClick(){
    let x = document.querySelector("#foobar");
    if (x){
        x.scrollIntoView();
    }
}

And it works as expected for internal links. This does not actually use anchor tags so it would not touch the URL at all.


All the other answers will work on Angular version < 6.1. But if you've got the latest version then you won't need to do these ugly hacks as Angular has fixed the issue.

here's the link to issue

All you'd need to do is set scrollOffset with the option of the second argument ofRouterModule.forRoot method.

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      scrollPositionRestoration: 'enabled',
      anchorScrolling: 'enabled',
      scrollOffset: [0, 64] // [x, y]
    })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {}

A little late but here's an answer I found that works:

<a [routerLink]="['/path']" fragment="test" (click)="onAnchorClick()">Anchor</a>

And in the component:

constructor( private route: ActivatedRoute, private router: Router ) {}

  onAnchorClick ( ) {
    this.route.fragment.subscribe ( f => {
      const element = document.querySelector ( "#" + f )
      if ( element ) element.scrollIntoView ( element )
    });
  }

The above doesn't automatically scroll to the view if you land on a page with an anchor already, so I used the solution above in my ngInit so that it could work with that as well:

ngOnInit() {
    this.router.events.subscribe(s => {
      if (s instanceof NavigationEnd) {
        const tree = this.router.parseUrl(this.router.url);
        if (tree.fragment) {
          const element = document.querySelector("#" + tree.fragment);
          if (element) { element.scrollIntoView(element); }
        }
      }
    });
  }

Make sure to import Router, ActivatedRoute and NavigationEnd at the beginning of your component and it should be all good to go.

Source


Use this for the router module in app-routing.module.ts:

@NgModule({
  imports: [RouterModule.forRoot(routes, {
    useHash: true,
    scrollPositionRestoration: 'enabled',
    anchorScrolling: 'enabled',
    scrollOffset: [0, 64]
  })],
  exports: [RouterModule]
})

This will be in your HTML:

<a href="#/users/123#userInfo">

Although Günter's answer is correct, it doesn't cover the "jump to" the anchor tag part.

Therefore, additionally to:

<a [routerLink]="['somepath']" fragment="Test">Jump to 'Test' anchor </a>
this._router.navigate( ['/somepath', id ], {fragment: 'test'});

... in the component (parent) where you need a "jump to" behavior, add:

import { Router, NavigationEnd } from '@angular/router';

class MyAppComponent {
  constructor(router: Router) {

    router.events.subscribe(s => {
      if (s instanceof NavigationEnd) {
        const tree = router.parseUrl(router.url);
        if (tree.fragment) {
          const element = document.querySelector("#" + tree.fragment);
          if (element) { element.scrollIntoView(true); }
        }
      }
    });

  }
}

Please note that this is a workaround! Follow this github issue for future updates. Credits to Victor Savkin for providing the solution!


Unlike other answers I'd additionally also add focus() along with scrollIntoView(). Also I'm using setTimeout since it jumps to top otherwise when changing the URL. Not sure what was the reason for that but it seems setTimeout does the workaround.

Origin:

<a [routerLink] fragment="some-id" (click)="scrollIntoView('some-id')">Jump</a>

Destination:

<a id="some-id" tabindex="-1"></a>

Typescript:

scrollIntoView(anchorHash) {
    setTimeout(() => {
        const anchor = document.getElementById(anchorHash);
        if (anchor) {
            anchor.focus();
            anchor.scrollIntoView();
        }
    });
}

Solutions above didn't work for me... This one did it:

First, prepare MyAppComponent for automatic scrolling in ngAfterViewChecked()...

import { Component, OnInit, AfterViewChecked } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';

@Component( {
   [...]
} )
export class MyAppComponent implements OnInit, AfterViewChecked {

  private scrollExecuted: boolean = false;

  constructor( private activatedRoute: ActivatedRoute ) {}

  ngAfterViewChecked() {

    if ( !this.scrollExecuted ) {
      let routeFragmentSubscription: Subscription;

      // Automatic scroll
      routeFragmentSubscription =
        this.activatedRoute.fragment
          .subscribe( fragment => {
            if ( fragment ) {
              let element = document.getElementById( fragment );
              if ( element ) {
                element.scrollIntoView();

                this.scrollExecuted = true;

                // Free resources
                setTimeout(
                  () => {
                    console.log( 'routeFragmentSubscription unsubscribe' );
                    routeFragmentSubscription.unsubscribe();
                }, 1000 );

              }
            }
          } );
    }

  }

}

Then, navigate to my-app-route sending prodID hashtag

import { Component } from '@angular/core';
import { Router } from '@angular/router';

@Component( {
   [...]
} )
export class MyOtherComponent {

  constructor( private router: Router ) {}

  gotoHashtag( prodID: string ) {
    this.router.navigate( [ '/my-app-route' ], { fragment: prodID } );
  }

}

Update

This is now supported

<a [routerLink]="['somepath']" fragment="Test">Jump to 'Test' anchor </a>
this._router.navigate( ['/somepath', id ], {fragment: 'test'});

Add Below code to your component to scroll

  import {ActivatedRoute} from '@angular/router'; // <-- do not forget to import

  private fragment: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.fragment.subscribe(fragment => { this.fragment = fragment; });
  }

  ngAfterViewInit(): void {
    try {
      document.querySelector('#' + this.fragment).scrollIntoView();
    } catch (e) { }
  }

Original

This is a known issue and tracked at https://github.com/angular/angular/issues/6595


Since the fragment property still doesn't provide anchor scrolling, this workaround did the trick for me:

<div [routerLink]="['somepath']" fragment="Test">
  <a href="#Test">Jump to 'Test' anchor </a>
</div>

A simple solution that works for pages without any query parameters, is browser back / forward, router and deep-linking compliant.

<a (click)="jumpToId('anchor1')">Go To Anchor 1</a>


ngOnInit() {

    // If your page is dynamic
    this.yourService.getWhatever()
        .then(
            data => {
            this.componentData = data;
            setTimeout(() => this.jumpToId( window.location.hash.substr(1) ), 100);
        }
    );

    // If your page is static
    // this.jumpToId( window.location.hash.substr(1) )
}

jumpToId( fragment ) {

    // Use the browser to navigate
    window.location.hash = fragment;

    // But also scroll when routing / deep-linking to dynamic page
    // or re-clicking same anchor
    if (fragment) {
        const element = document.querySelector('#' + fragment);
        if (element) element.scrollIntoView();
    }
}

The timeout is simply to allow the page to load any dynamic data "protected" by an *ngIf. This can also be used to scroll to the top of the page when changing route - just provide a default top anchor tag.


This one work for me !! This ngFor so it dynamically anchor tag, You need to wait them render

HTML:

<div #ngForComments *ngFor="let cm of Comments">
    <a id="Comment_{{cm.id}}" fragment="Comment_{{cm.id}}" (click)="jumpToId()">{{cm.namae}} Reply</a> Blah Blah
</div>

My ts file:

private fragment: string;
@ViewChildren('ngForComments') AnchorComments: QueryList<any>;

ngOnInit() {
      this.route.fragment.subscribe(fragment => { this.fragment = fragment; 
   });
}
ngAfterViewInit() {
    this.AnchorComments.changes.subscribe(t => {
      this.ngForRendred();
    })
}

ngForRendred() {
    this.jumpToId()
}

jumpToId() { 
    let x = document.querySelector("#" + this.fragment);
    console.log(x)
    if (x){
        x.scrollIntoView();
    }
}

Don't forget to import that ViewChildren, QueryList etc.. and add some constructor ActivatedRoute !!


In html file:

<a [fragment]="test1" [routerLink]="['./']">Go to Test 1 section</a>

<section id="test1">...</section>
<section id="test2">...</section>

In ts file:

export class PageComponent implements AfterViewInit, OnDestroy {

  private destroy$$ = new Subject();
  private fragment$$ = new BehaviorSubject<string | null>(null);
  private fragment$ = this.fragment$$.asObservable();

  constructor(private route: ActivatedRoute) {
    this.route.fragment.pipe(takeUntil(this.destroy$$)).subscribe(fragment => {
      this.fragment$$.next(fragment);
    });
  }

  public ngAfterViewInit(): void {
    this.fragment$.pipe(takeUntil(this.destroy$$)).subscribe(fragment => {
      if (!!fragment) {
        document.querySelector('#' + fragment).scrollIntoView();
      }
    });
  }

  public ngOnDestroy(): void {
    this.destroy$$.next();
    this.destroy$$.complete();
  }
}

I've just tested very useful plugin available in nmp - ngx-scroll-to, which works great for me. However it's designed for Angular 4+, but maybe somebody will find this answer helpful.


After reading all of the solutions, I looked for a component and I found one which does exactly what the original question asked for: scrolling to anchor links. https://www.npmjs.com/package/ng2-scroll-to

When you install it, you use syntax like:

// app.awesome.component.ts
@Component({
   ...
   template: `...
        <a scrollTo href="#main-section">Scroll to main section</a>
        <button scrollTo scrollTargetSelector="#test-section">Scroll to test section</a>
        <button scrollTo scrollableElementSelector="#container" scrollYTarget="0">Go top</a>
        <!-- Further content here -->
        <div id="container">
            <section id="main-section">Bla bla bla</section>
            <section id="test-section">Bla bla bla</section>
        <div>
   ...`,
})
export class AwesomeComponent {
}

It has worked really well for me.


I tried most of these solutions but ran into problems leaving and coming back with another fragment it wouldn't work, so I did something a bit different that works 100%, and gets rid of the ugly hash in the URL.

tl;dr here's a better way than what I've seen so far.

import { Component, OnInit, AfterViewChecked, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';

@Component({
    selector: 'app-hero',
    templateUrl: './hero.component.html',
    styleUrls: ['./hero.component.scss']
})
export class HeroComponent implements OnInit, AfterViewChecked, OnDestroy {
    private fragment: string;
    fragSub: Subscription;

    constructor(private route: ActivatedRoute) { }

    ngOnInit() {
        this.fragSub = this.route.fragment.subscribe( fragment => { this.fragment = fragment; })
    }

    ngAfterViewChecked(): void {
        try {
            document.querySelector('#' + this.fragment).scrollIntoView({behavior: 'smooth'});
            window.location.hash = "";
          } catch (e) { }
    }

    ngOnDestroy() {
        this.fragSub.unsubscribe();
    }
}

I just got this working on my own website, so I figured it would be worth posting my solution here.

<a [routerLink]="baseUrlGoesHere" fragment="nameOfYourAnchorGoesHere">Link Text!</a>

<a name="nameOfYourAnchorGoesHere"></a>
<div>They're trying to anchor to me!</div>

And then in your component, make sure you include this:

 import { ActivatedRoute } from '@angular/router';

 constructor(private route: ActivatedRoute) { 
     this.route.fragment.subscribe ( f => {
         const element = document.querySelector ( "#" + f )
         if ( element ) element.scrollIntoView ( element )
     });
 }

Here is another workaround with refernce to JavierFuentes answer:

<a [routerLink]="['self-route', id]" fragment="some-element" (click)="gotoHashtag('some-element')">Jump to Element</a>

in script:

import {ActivatedRoute} from "@angular/router";
import {Subscription} from "rxjs/Subscription";

export class Links {
    private scrollExecuted: boolean = false;

    constructor(private route: ActivatedRoute) {} 

    ngAfterViewChecked() {
            if (!this.scrollExecuted) {
              let routeFragmentSubscription: Subscription;
              routeFragmentSubscription = this.route.fragment.subscribe(fragment => {
                if (fragment) {
                  let element = document.getElementById(fragment);
                  if (element) {
                    element.scrollIntoView();
                    this.scrollExecuted = true;
                    // Free resources
                    setTimeout(
                      () => {
                        console.log('routeFragmentSubscription unsubscribe');
                        routeFragmentSubscription.unsubscribe();
                      }, 0);
                  }
                }
              });
            }
          }

        gotoHashtag(fragment: string) {
            const element = document.querySelector("#" + fragment);
            if (element) element.scrollIntoView(element);
        }
}

This allows user to directly scroll to element, if user directly lands on the page having hashtag in url.

But in this case, I have subscribed route Fragment in ngAfterViewChecked but ngAfterViewChecked() gets called continuously per every ngDoCheck and it doesn't allows user to scroll back to top, so routeFragmentSubscription.unsubscribe is called after a timeout of 0 millis after view is scrolled to element.

Additionally gotoHashtag method is defined to scroll to element when user specifically clicks on the anchor tag.

Update:

If url has querystrings, [routerLink]="['self-route', id]" in anchor wont preserve the querystrings. I tried following workaround for the same:

<a (click)="gotoHashtag('some-element')">Jump to Element</a>

constructor( private route: ActivatedRoute,
              private _router:Router) {
}
...
...

gotoHashtag(fragment: string) {
    let url = '';
    let urlWithSegments = this._router.url.split('#');

    if(urlWithSegments.length){
      url = urlWithSegments[0];
    }

    window.location.hash = fragment;
    const element = document.querySelector("#" + fragment);
    if (element) element.scrollIntoView(element);
}

Adding on to Kalyoyan's answer, this subscription is tied to the router and will live until the page is fully refreshed. When subscribing to router events in a component, be sure to unsubscribe in ngOnDestroy:

import { OnDestroy } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { Subscription } from "rxjs/Rx";

class MyAppComponent implements OnDestroy {

  private subscription: Subscription;

  constructor(router: Router) {
    this.subscription = router.events.subscribe(s => {
      if (s instanceof NavigationEnd) {
        const tree = router.parseUrl(router.url);
        if (tree.fragment) {
          const element = document.querySelector("#" + tree.fragment);
          if (element) { element.scrollIntoView(element); }
        }
      }
    });
  }

  public ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

if it does not matter to have those element ids appended to the url, you should consider taking a look at this link:

Angular 2 - Anchor Links to Element on Current Page

_x000D_
_x000D_
// html_x000D_
// add (click) event on element_x000D_
<a (click)="scroll({{any-element-id}})">Scroll</a>_x000D_
_x000D_
// in ts file, do this_x000D_
scroll(sectionId) {_x000D_
let element = document.getElementById(sectionId);_x000D_
_x000D_
  if(element) {_x000D_
    element.scrollIntoView(); // scroll to a particular element_x000D_
  }_x000D_
 }
_x000D_
_x000D_
_x000D_


I had the same issue. The solution : using View port Scroller https://angular.io/api/common/ViewportScroller#scrolltoanchor

-- app-routing.module.ts code :

import { PageComponent } from './page/page.component';

const routes: Routes = [
   path: 'page', component: PageComponent },
   path: 'page/:id', component: PageComponent }
];

-- Component HTML

  <a (click) = "scrollTo('typeExec')">
    <mat-icon>lens</mat-icon>
  </a>

-- Component's code :

    import { Component } from '@angular/core';
    import { ViewportScroller } from '@angular/common';


    export class ParametrageComponent {

      constructor(private viewScroller: ViewportScroller) {}

      scrollTo(tag : string)
      {
        this.viewScroller.scrollToAnchor(tag);
      }

    }

Examples related to routing

Send data through routing paths in Angular Angular2 Routing with Hashtag to page anchor Passive Link in Angular 2 - <a href=""> equivalent laravel throwing MethodNotAllowedHttpException How to use absolute path in twig functions S3 Static Website Hosting Route All Paths to Index.html AngularJS - How can I do a redirect with a full page load? Web API Routing - api/{controller}/{action}/{id} "dysfunctions" api/{controller}/{id} The requested resource does not support HTTP method 'GET' My Routes are Returning a 404, How can I Fix Them?

Examples related to angular

error NG6002: Appears in the NgModule.imports of AppModule, but could not be resolved to an NgModule class error TS1086: An accessor cannot be declared in an ambient context in Angular 9 TS1086: An accessor cannot be declared in ambient context @angular/material/index.d.ts' is not a module Why powershell does not run Angular commands? error: This is probably not a problem with npm. There is likely additional logging output above Angular @ViewChild() error: Expected 2 arguments, but got 1 Schema validation failed with the following errors: Data path ".builders['app-shell']" should have required property 'class' Access blocked by CORS policy: Response to preflight request doesn't pass access control check origin 'http://localhost:4200' has been blocked by CORS policy in Angular7

Examples related to hashtag

Angular2 Routing with Hashtag to page anchor How to handle anchor hash linking in AngularJS Get all photos from Instagram which have a specific hashtag with PHP