24

Trying to scroll to an anchor link using the following syntax.

<a [routerLink]="['./']" fragment="test">Testing</a>

And the anchor node looks like this

<div id="test">

When clicked the browser address bar shows the #test fragment but the automatic scrolling does not occur. Any idea why it does not scroll?

doorman
  • 15,707
  • 22
  • 80
  • 145

10 Answers10

26

Based on @vsavkin workaround and taking advantage that fragments are provided as an observable (using "@angular/core": "^2.3.1"):

class MyAppComponent implements OnInit{

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.fragment.subscribe(f => {
      const element = document.querySelector("#" + f)
      if (element) element.scrollIntoView()
    })
  }
}
Machado
  • 8,965
  • 6
  • 43
  • 46
pearpages
  • 18,703
  • 1
  • 26
  • 27
11

From Angular 6.1 there is anchorScrolling for the router:

  1. Set this in app.module.ts

       imports: [
         RouterModule.forRoot(routes, {
           scrollPositionRestoration: 'enabled', // or 'top'
           anchorScrolling: 'enabled',
           scrollOffset: [0, 64] // [x, y] - adjust scroll offset
         })
       ],
       exports: [RouterModule]
    
  2. html

     <div id="test">
       // contents goes here...
     </div>
    
    
    
     <a [routerLink]="['./']" fragment="test">Testing</a>
    
  3. Import now the viewScroller which Angular v6 new feature (this might be not neccesary):

     import { ViewportScroller } from '@angular/common';
    
     constructor( private viewportScroller: ViewportScroller ) 
    
     ...
    
     scrollToTest() { 
       this.viewportScroller.scrollToAnchor('test');
     }
    
David
  • 4,336
  • 2
  • 23
  • 31
frogcoder
  • 354
  • 5
  • 19
8

This is the extension of https://stackoverflow.com/a/52415783/9646878 with extra config onSameUrlNavigation: 'reload'

For me setTimeout worked.

complete example :

  1. Set this in app.module.ts
       imports: [
         RouterModule.forRoot(routes, {
           scrollPositionRestoration: 'enabled', // or 'top'
           anchorScrolling: 'enabled',
           scrollOffset: [0, 64], // [x, y] - adjust scroll offset
           onSameUrlNavigation: 'reload'
         })
       ],
       exports: [RouterModule]
  1. app.component.html
<p>
 <div id = "componentId"> </div>
</p>
  1. app.component.ts
onScrollTo(location: string){
  setTimeout(() => { this.router.navigate([], { fragment: location }); }, 500);
}
use this method on Button click.

Alternate Approach

I used this approach for scrolling on samepage. 1.component.ts

  scrollTo(fragment): void {
    this.router.navigate([], { fragment: fragment }).then(res => {
      const element = document.getElementById(fragment);
      if (element != undefined) element.scrollIntoView();
    });
  }

2.component.html

<section id="some-section">
 <div></div>
 ....
</section>

3.call the method

<a (click)="scrollTo('some-section')">Navigate to Fragment</a>
  • For me, Alternate Approach along with setTimeout worked. I was doing *ngIf for checking so I had to skip a couple of lifecycles to make sure my HTML element is visible in the UI. – mark922 Mar 13 '21 at 06:50
3

I suppose scrolling isn't implemented with angular 2 yet. My solution to similar problem (scrolling to anchor on the same page) was to use ng2-page-scroll.

Leukonoe
  • 649
  • 2
  • 10
  • 29
3

Look in Angular 6 new feature ViewportScroller

https://angular.io/api/common/ViewportScroller

Davis
  • 2,937
  • 3
  • 18
  • 28
2

I can suggest to user ng2-page-scroll

ng2-page-scroll

install

npm install ng2-page-scroll --save

import in your app.module.ts

import {Ng2PageScrollModule} from 'ng2-page-scroll';

@NgModule({
    imports: [
        /* Other imports here */
        Ng2PageScrollModule
        ]
})
export class AppModule {
}

test it in your html component

<a pageScroll href="#test">Testing</a>
<div id="test">
2

I have recreated an reusalbe service that can be used in any component in order to either scroll to fragment if present else to top of the page.The following contains the complete echo system in angular anchoring use through reusable service approach.

//(In service.ts)
import { Injectable } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ViewportScroller } from '@angular/common';

@Injectable({
  providedIn: 'root'
})
export class RouterPathService {

  constructor(
    private activatedRoute: ActivatedRoute,
    private viewportScroller: ViewportScroller,


  ) { }

  scroll() {
    this.activatedRoute.fragment.subscribe((fragment: string) => {
      if (fragment) {
        this.scrollToAnchor(fragment)
      } else {
        this.scrollToTop();
      }
    });
  }

  scrollToTop() {
    this.viewportScroller.scrollToPosition([0, 0])

  }

  scrollToAnchor(elementId: string): void {
    this.viewportScroller.scrollToAnchor(elementId);
  }
}

We can call function from above reusable service from any component as :

//(In component.ts)
......
constructor(
        private routerPathService: RouterPathService,
     
    ) {
        this.routerPathService.scroll();
    }
....

HTML component would like :

<a [routerLink]="['/my-route']" fragment="test">Testing</a>

And app-routing.module.ts should enable the anchoring as:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
.........
];

@NgModule({
    imports: [RouterModule.forRoot(routes, {
        anchorScrolling: 'enabled',
    })],
    exports: [RouterModule]
})
export class AppRoutingModule { }
RameshD
  • 912
  • 7
  • 6
1

ViewportScroller doesn't work fine on IE if your content is loaded dynamicly.

Instead you need to implement AfterViewChecked

TestComponent implements AfterViewChecked{

     ngAfterViewChecked(){
        var element = document.getElementById("youranchor")
        element.scrollIntoView(false)
      }
}

for detail,see https://angular.io/api/core/AfterViewChecked

Langy
  • 315
  • 2
  • 6
0

I liked using this

scroll(container, target) {
    let scrollTo = target.getBoundingClientRect().top;

    $(container).animate({
      scrollTop: `+=${scrollTo}`
    }, 900);
}

then in HTML do something like

<div #container> <!-- Scrolling container -->
    <button (click)="scroll(container, intro)">Go To Intro</button>
    <!-- other content -->
    <div #intro></div>
</div>
maburdi94
  • 41
  • 2
0

My scenario was that after pressing a button at the top of the screen and receiving some data from backend I may have a message to allow the scrolling to a certain point. If the buttom is pressed again I need to be able to scroll again and again...

After using successfully

this.viewportScroller.scrollToAnchor

or

scrollIntoView

locally, I found out that it was not working in production environment (really don't know why). So,

router.navigate([], { fragment: 'yourFragment' });

Did the job, and of course I had to "clean" the fragment to enable future scrollings, so inside button's logic I added:

router.navigate(['/']);

Important: remember to configure the ExtraOptions inside app-routing.module.ts