Angular4 - Scrolling to anchor
Asked Answered
S

9

47

I am trying to do a simple scroll to an anchor element on the same page. Basically, the person clicks on a "Try It" button and it scrolls to an area lower on the page with the id "login". Right now, it is working with a basic id="login" & <a href="#login"></a> but it is jumping to that section. Ideally, I would like it to scroll there. If I am using Angular4, is there some built in way to do this or what is the easiest way? Thanks!

Whole template... (component still empty)

<div id="image-header">
    <div id="intro">
        <h1 class="text-center">Welcome to ThinkPlan</h1>
        <h3 class="text-center">Planning your way</h3>
        <a class="btn btn-outline-primary" href="#login" id="view">Try It</a> <!-- Where the user clicks to scroll -->
    </div>
</div>
<div class="container" id="info">
    <div class="row">
        <div class="col-md-12">
             <div class="space" id="login"></div> <!-- The place to scroll to -->
                <h1 class="text-center">ThinkPlan</h1>
                <form  class="form-horizontal">
                    <fieldset>
                        <legend>Login</legend>
                        <div class="form-group">
                            <label for="inputEmail" class="col-lg-2 control-label">Email</label>
                            <div class="col-lg-10">
                                <input type="text" class="form-control" id="inputEmail" placeholder="Email">
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="inputPassword" class="col-lg-2 control-label">Password</label>
                            <div class="col-lg-10">
                                <input type="password" class="form-control" id="inputPassword" placeholder="Password"> 
                            </div>
                        </div>
                        <div class="form-group" id="log-btn">
                            <div class="col-lg-10 col-lg-offset-2">
                                <button routerLink="/plan" type="submit" class="btn btn-outline-primary">Login</button>
                            </div>
                        </div>
                        <p class="lead text-center">New here? <a routerLink="signup">Sign up.</a></p>
                    </fieldset>
                </form>
        </div>
    </div>
</div>
Sloe answered 8/6, 2017 at 16:28 Comment(1)
#46659022Tintinnabulum
P
63

For Angular 6+ you can use:

  1. In your routing module:

     imports: [RouterModule.forRoot(routes, { anchorScrolling: 'enabled'})]
    
  2. Then in your component html

     <a (click)="onClick('AnchorId')">Click me!</a>
     <a (click)="onClick('OtherAnchorId')">Click me, too!</a>
     ...
     <div id="AnchorId">...</div>
     ...
     <div id="OtherAnchorId">...</div>
    
  3. Then in your component ts

    import { ViewportScroller } from '@angular/common';
    
    @Component({
        selector: 'component-selector',
        templateUrl: './mycomponent.component.html'
    })
    export class MyComponent {
      constructor(private viewportScroller: ViewportScroller) {}
    
      public onClick(elementId: string): void { 
          this.viewportScroller.scrollToAnchor(elementId);
      }
    }
    
Perez answered 3/11, 2019 at 21:35 Comment(4)
nice one but you don't need the imports: [RouterModule.forRoot(routes, { anchorScrolling: 'enabled'})] the viewportScroller does not need the routermodule as a dependencySense
Even though Kim Kern answer is correct, this feels more like "Angular framework". You can also add css: "html {scroll-behavior: smooth;}" to make it more pleasant.Baroness
Perfect, but how about on-Scroll event?Burkhardt
This is useful for on page section scroll. To open URL in new tab and to redirect to specific section check @Sarah Dubois answer.Guidotti
I
30

Automatic Scrolling

Since Angular v6, there is the new anchorScrolling option for the RouterModule. You can set it in the module's import:

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

With this, Angular will automatically scroll to the element with the id of the given fragment.

⚠️ However, this does not work when the data is loaded asynchronously, see this Github issue. ⚠️


Manual Scrolling

Smooth scrolling is not yet possible via the RouterModule, but you can do it manually:

1) Get your target e.g. with ViewChild:

@ViewChild('pageInfo') pageInfo: ElementRef; 

2) Call scrollIntoView on the nativeElement:

const targetElement = this.pageInfo.nativeElement
targetElement.scrollIntoView({behavior: "smooth"})
Intermolecular answered 9/9, 2018 at 3:7 Comment(6)
This is spot on. Thanks. To add to this for anyone else, to get the target ref use a ViewChild; @ViewChild('pageInfo') elemPageInfo: ElementRef; this.elemPageInfo.nativeElement.scrollIntoView({behavior: 'smooth'});Hampton
@Ralphonzo Glad to hear. :-) Thanks for the great addition, I have edited the answer.Intermolecular
Please elaborate what is the "targetElement" in option 2. It is the scrollable element?Stempson
@ShacharHar-Shuv targetElement is the element that you want to scroll to so that it is visible in the viewport of your browser.Intermolecular
Link to the Angular documentation about anchorScrolling hereEngel
@KimKem/@Ralpharoo, I have a navbar on top which is in fixed position. When used this method the scrolled element is positioned at the top level and goes underneath the navbar. Is it possible to define the top position with scrollIntoView method.Toner
R
28

I think it's more simple solution :

In your HTML :

<a [routerLink]="['/']" fragment="login"></a>

In your typescript :

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

ngAfterViewChecked(): void {
  try {
      if(this.fragment) {
          document.querySelector('#' + this.fragment).scrollIntoView();
      }
  } catch (e) { }
}
Repetitive answered 29/11, 2017 at 16:38 Comment(7)
This redirects to root route. To add a hash on same page location, it works better using <a routerLink="." fragment="login"></a>.Barbellate
This approach is better then 'npm install ng2-page-scroll'. But it works without smooth scrollingKollwitz
You can get smooth scrolling like this: scrollIntoView({behavior: "smooth"})Literalminded
ugh, what is this.route??Castrate
@SarahDubois this is a really great solution and works great for me using angular version 8. Five stars for its simplicity. @Castrate this.route is ActivatedRoute. you might need these imports import { ActivatedRoute, Router, NavigationEnd } from '@angular/router'; I can post an answer with more details.Ours
@eosimosu the smooth scroll does not work on edge browser, but on chrome it works, should I make any changes to work here as well?Teerell
this also works when URL is open directly in the new tab and redirects to a specific section. e.g. www.example.com#loginGuidotti
E
10

Moerdern one line solution:

export class Component implements AfterViewInit {

  constructor(
    private viewportScroller: ViewportScroller
  ) { }

  ngAfterViewInit(): void {
    this.route.fragment.pipe(
      first()
    ).subscribe(fragment => this.viewportScroller.scrollToAnchor(fragment));
  }
}
Exhilaration answered 3/10, 2020 at 14:39 Comment(0)
A
4

So, I just spent like an hour on this with the latest Angular and other solutions worked OK for initial navigation but no matter what I tried, I could not get the anchor scrolling to work with navigation history.

In other words, when I went "back" or "forward" in my browser, the URL would update but I could not get Angular to scroll to the proper place. From logging, it seems that Angular was not actually acknowledging the fragment when going back/forward and so the scrolling was not being triggered.

I got fed up and decided to subscribe to the URL and manually scroll when a fragment is found, here's the code:

ngOnInit() {
  this.router.events.subscribe(val => {
    if (val instanceof NavigationEnd) {
      let fragmentIdx = val.urlAfterRedirects.lastIndexOf('#');
      if (fragmentIdx >= 0 && fragmentIdx < val.urlAfterRedirects.length - 1) {
        let fragment = val.urlAfterRedirects.substring(fragmentIdx+1);
        console.log('fragment: ' + fragment);
        document.getElementById(fragment).scrollIntoView();
      }
    }
  })
}

Just for clarity, you have to specify an id for the target element to scroll to.

Also, here's what my link looks like:

<a routerLink="." fragment="anchor-name">Scroll To Anchor</a>

Now, I can visit different anchors and see the scrolling as I go back/forward in history. Hope it helps someone!

Atalante answered 21/6, 2020 at 1:45 Comment(0)
P
3

With Angular 4 you can have some problem using anchor to the same page.

I solved using this way

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">
Plaid answered 5/10, 2017 at 13:58 Comment(2)
thank you for this comprehensive answer, I tried it out but the npm-module has outdated peer dependencies with the latest angular version.Catinacation
it doesn't work. at list with angular 6. my page is reloading after clickingKollwitz
C
3

AfterViewCheck() will be called every time the ChangeDetection is triggered, so it is a bad solution.

Use AfterViewInit() like

public ngAfterViewInit(): void {
  this.subscription = this.route.fragment
    .subscribe(fragment => {
        const targetElement = this.document.querySelector('#' + fragment);
        if (fragment && targetElement) {
            targetElement.scrollIntoView();
        } else {
            window.scrollTo(0, 0);
        }
    });
}
public ngOnDestroy(): void {
    this.subscription.unsubscribe();
}
Chaparral answered 7/12, 2018 at 14:50 Comment(1)
Why do we need to unsubscribe the subscription manually in ngOnDestroy ? I thought angular automatically does it as it's a route subscription.Torhert
O
1

This is a more detailed example to add to Sarah Dubois' answer above.

Import router modules.

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

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

  router.events.subscribe(event => {
    if (event instanceof NavigationEnd) {    
      if (event.url) {
        this.route.fragment.subscribe(fragment => this.fragment = fragment);
      }
    }
  });

}

ngAfterViewChecked(): void {

  try {
    if(this.fragment != null) {
     document.querySelector("a[name="+this.fragment+"]").scrollIntoView();
    }
  } catch (e) {
    //console.log(e, 'error');
  }

}

I like the use of querySelector which will give you a lot of options to which element you would like to scroll to. https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector

This works with Angular 8 :)

Ours answered 7/8, 2019 at 18:47 Comment(0)
C
1

Finally, works for me in Angular 7:

Html:

<h1 id='here'>Here</h1>

Component:

  ngAfterViewInit() {
    this.route.fragment.subscribe(fragment => {
      this.fragment = fragment;
      setTimeout(() => this.scrollToAnchor(), 10);
    });
  }

  scrollToAnchor(): void {
    try {
      if (this.fragment) {
        document.querySelector('#' + this.fragment).scrollIntoView();
      }
    } catch (e) { }
  }
  goToHere():void{
    this.router.navigate(['/product'], { fragment: 'here' });
  }
Compress answered 29/1, 2020 at 10:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.