46

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>
lnamba
  • 1,681
  • 3
  • 18
  • 26

9 Answers9

62

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);
      }
    }
    
0lukasz0
  • 3,155
  • 1
  • 24
  • 40
  • 3
    nice one but you don't need the imports: [RouterModule.forRoot(routes, { anchorScrolling: 'enabled'})] the viewportScroller does not need the routermodule as a dependency – Michael P. Feb 12 '20 at 03:02
  • 8
    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. – Kunepro Feb 12 '20 at 09:48
  • 1
    Perfect, but how about on-Scroll event? – AmirReza-Farahlagha Jun 13 '20 at 12:40
  • This is useful for on page section scroll. To open URL in new tab and to redirect to specific section check @Sarah Dubois answer. – Keyul Aug 14 '20 at 16:17
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"})
Kim Kern
  • 54,283
  • 17
  • 197
  • 195
  • 1
    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'}); – Ralpharoo Apr 03 '19 at 23:18
  • @Ralphonzo Glad to hear. :-) Thanks for the great addition, I have edited the answer. – Kim Kern Apr 04 '19 at 08:34
  • Please elaborate what is the "targetElement" in option 2. It is the scrollable element? – Shachar Har-Shuv Jul 04 '19 at 16:11
  • @ShacharHar-Shuv `targetElement` is the element that you want to scroll to so that it is visible in the viewport of your browser. – Kim Kern Jul 05 '19 at 07:42
  • Link to the Angular documentation about anchorScrolling [here](https://angular.io/api/router/ExtraOptions#anchorScrolling) – Hadrien TOMA Jan 09 '21 at 09:07
  • @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. – hemant Apr 05 '21 at 14:35
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) { }
}
Sarah Dubois
  • 297
  • 3
  • 4
  • 5
    This redirects to root route. To add a hash on same page location, it works better using ``. – Kapcash Apr 26 '18 at 10:07
  • This approach is better then 'npm install ng2-page-scroll'. But it works without smooth scrolling – Dmitry Grinko Aug 08 '18 at 10:47
  • 6
    You can get smooth scrolling like this: `scrollIntoView({behavior: "smooth"})` – Emmanuel Osimosu Aug 29 '18 at 10:05
  • 3
    ugh, what is `this.route`?? – Phil Jun 19 '19 at 22:19
  • 3
    @SarahDubois this is a really great solution and works great for me using angular version 8. Five stars for its simplicity. @Phil `this.route` is ActivatedRoute. you might need these imports `import { ActivatedRoute, Router, NavigationEnd } from '@angular/router';` I can post an answer with more details. – Ian Poston Framer Aug 07 '19 at 18:28
  • @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? – Sammy J Nov 07 '19 at 05:13
  • this also works when URL is open directly in the new tab and redirects to a specific section. e.g. www.example.com#login – Keyul Aug 14 '20 at 16:16
8

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));
  }
}
Tilo
  • 605
  • 7
  • 15
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!

GoForth
  • 573
  • 7
  • 10
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">
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();
}
Sergey Andreev
  • 1,398
  • 3
  • 16
  • 26
  • 1
    Why do we need to unsubscribe the subscription manually in ngOnDestroy ? I thought angular automatically does it as it's a route subscription. – Chandan Agarwal Feb 06 '20 at 09:41
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' });
  }
0

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 :)

Ian Poston Framer
  • 938
  • 12
  • 16