Angular2 Routing with Hashtag to page anchor

2019-01-01 10:25发布

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.

19条回答
倾城一夜雪
2楼-- · 2019-01-01 10:58

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();
        }
    });
}
查看更多
听够珍惜
3楼-- · 2019-01-01 11:00

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 } );
  }

}
查看更多
只若初见
4楼-- · 2019-01-01 11:01

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

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

-- Component's code :

export class ParametrageComponent {

  constructor(private viewScroller: ViewportScroller) {}

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

}
查看更多
永恒的永恒
5楼-- · 2019-01-01 11:02

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.

查看更多
忆尘夕之涩
6楼-- · 2019-01-01 11:02

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);
}
查看更多
还给你的自由
7楼-- · 2019-01-01 11:03

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.

查看更多
登录 后发表回答