ページアンカーへのハッシュタグを使用したAngular2ルーティング


120

Angular2ページにいくつかのリンクを追加したいと思います。クリックすると、通常のハッシュタグのように、そのページ内の特定の位置にジャンプします。したがって、リンクは次のようなものになります

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

通常のAngular2の方法で問題ないので、HashLocationStrategyは必要ないと思いますが、直接追加すると、リンクは実際には同じページのどこかではなくルートにジャンプします。どんな方向でも感謝します。

回答:


130

更新

これは現在サポートされています

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

以下のコードをコンポーネントに追加してスクロールします

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

元の

これは既知の問題であり、https://github.com/angular/angular/issues/6595で追跡されます


1
(何例えば文字列を変数@invot 123ルートパスパラメータのような期待と仮定問題であるが){ path: 'users/:id', ....}
ギュンターZöchbauer

2
アンカーまでスクロールしたい場合は、この投稿を参照してください:github.com/angular/angular/issues/6595
Pere Pages

12
注:これはまだスクロールしない
マルティン・コル

2
はい、それはsetTimeoutで動作します、私がより良い解決策を見つけたら、私はあなたに知らせます
Amr Ibrahim

1
数字のようなIDにスクロールするのに苦労している人のために、それは有効なCSSセレクターである01100、無効であることに注意してください。有効なセレクターにするために、文字などを追加したい場合があります。だから、あなたはまだ通過する01フラグメントとして、しかし、idのようなものである必要があるd01ため、document.querySelector('#d'+id)一致します。
ポップ

52

ギュンターの答えが正しいか、それはアンカータグ部「へのジャンプ」をカバーしていません

したがって、さらに:

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

...「ジャンプ」動作が必要なコンポーネント(親)に、以下を追加します。

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

  }
}

これは回避策であることに注意してください!今後のアップデートについては、このgithubの問題をフォローしてください。ソリューションを提供してくれVictor Savkinの功績です!


こんにちは、ページ上部のリストで定義された質問をクリックして回答にジャンプできるFAQページを作成しています。そのため、ユーザーはアンカーにジャンプしたときに既に現在のページにいます。routerLink属性を機能させたい場合"['../faq']"は、値として指定する必要があります。そうしないと、/ faq /#anchorの代わりに/ faq / faq /#anchorにジャンプしようとします。これは正しい方法ですか、それともrouterlinkの現在のページを参照するよりエレガントな方法がありますか?また、document.querySelector("#" + tree.fragment);有効でないセレクタエラーが表示されます。これは正しいですか?ありがとう
モーリス

2
同じリンクをもう一度クリックしても機能しません。ユーザーが同じアンカーリンクをクリックした場合、誰かがこれを機能させました<a [routerLink]="['/faq']" fragment="section6">か?
ジュニアメイヘ

@JuniorMこれを理解したことはありますか?同じ問題が発生しています。
マフィンマン


1
これはより多くの露出を必要とします。これはIMOのより良い答えです。ほとんどの人はセクションにジャンプしたいと思うでしょう。
iamtravisw

44

少し遅く答えてすみません。Angular Routing Documentationには、ハッシュタグをページアンカーにルーティングするための事前定義された関数があります。つまり、 anchorScrolling: 'enabled'です。

ステップ-1:-最初にルーターモジュールをapp.module.tsファイルにインポートします:-

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

ステップ-2:-HTMLページに移動し、ナビゲーションを作成し、[routerLink]のような2つの重要な属性と、それぞれのDiv IDを照合するためのフラグメントを追加します。

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

ステップ-3: -ID フラグメントを照合してセクション/ divを作成します:-

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

参考までに、問題の解決に役立つ小さなデモを作成して、以下の例を追加しました。

デモ: https : //routing-hashtag-page-anchors.stackblitz.io/


どうもありがとうございました。クリーンで簡潔に機能します。
Belmiris 2018

2
はい、おかげで、角度7でページのロードの自動scollのために、ちょうど追加する必要がありますscrollPositionRestoration: 'enabled',:) anchorScrollingオプションの下に
ミカエル・

これにより、URLの末尾にハッシュが適切に追加されますが、同じ名前のdivにアンカーされません。何が欠けているのかわかりません。上記の3つの手順を実行しました。
oaklandrichie

@oaklandrichie:ここでjsfiddle / stackblitzコードを共有できますか?私はあなたを助けることができます
Naheed Shareef

この答えは明確に受け入れられるべきであり、魅力のように機能します!
キスKoppány

25

少し遅れましたが、ここで私が見つけた答えはうまくいきます:

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

そしてコンポーネントでは:

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

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

上記のコードは、アンカーのあるページに既に到達しても自動的にビューにスクロールしないため、ngInitで上記のソリューションを使用して、それでも動作するようにしました。

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

コンポーネントの最初にルーター、アクティブ化ルート、ナビゲーション終了を必ずインポートしてください。問題なく動作するはずです。

ソース


4
私のために働く!既にアクセスしている同じページ内を移動する場合は、[routerLink] = "['。']"を使用します
Raoul

1
さらに説明してもらえますか?この部分document.querySelector ( "#" + f )は文字列ではなくセレクターを想定しているため、エラーになります。
モーリス

1
@Maurice私にとってこれは機能します:(関数にelement.scrollIntoView()渡さなくelementても)。スムーズにするには、これを使用しますelement.scrollIntoView({block: "end", behavior: "smooth"})
B氏

ここのIntellisenseは、内onAnchorClick()で、ブール値をscrollIntoView:に渡す必要があることを示していますif (element) { element.scrollIntoView(true); }。これで、同じリンクを2回クリックして、作品をスクロールできます
JuniorMayhéAug

18

以前の答えはどれも私にとってはうまくいきませんでした。最後の努力で、私は自分のテンプレートで試しました:

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

これで私の.tsに:

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

そして、それは内部リンクに対して期待通りに機能します。これは実際にはアンカータグを使用しないため、URLにはまったく影響しません。


1
シンプルで簡単
WasiF

6

上記の解決策は私にはうまくいきませんでした...これはそれでうまくいきました:

まず、ngAfterViewChecked()MyAppComponentで自動スクロールの準備をします...

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

              }
            }
          } );
    }

  }

}

次に、ハッシュタグのmy-app-route送信に移動しますprodID

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

}

コメントや説明なしで投票を拒否...それは良い習慣ではありません...
JavierFuentes

これは私のために働いた!なぜngInitの代わりにngAfterViewCheckedを使用するのですか?
Antoine Boisier-Michaud

肯定的なフィードバックをありがとう@ AntoineBoisier-Michaud。ngInitがプロジェクトで必要なときに常に起動しません... ngAfterViewCheckedが起動します。このソリューションに賛成できますか?ありがとう。
JavierFuentes 2017年

6

他のすべての回答は、Angularバージョン<6.1で機能します。ただし、最新バージョンを入手している場合は、Angularが問題を修正しているため、これらの醜いハックを行う必要はありません。

ここに問題へのリンクがあります

メソッドのscrollOffset2番目の引数のオプションを設定するだけで済みますRouterModule.forRoot

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

2
これは外部リンクで機能しますか?別のウェブサイトで「www.abc.com#sectionToScrollTo」をクリックします
Sushmit Sagar

* ngIfを頻繁に使用する場合、anchorScrollingは機能しません。これは、ジャンプが早いためです:-(
Jojo.Lechelt

私がこれで抱えていた唯一の問題はタイミングです-要素のスタイルが完全にレンダリングされる前にアンカーにジャンプする傾向があり、位置がずれます。遅延を追加できるといいですね:)
チャーリー

5

これを次のルータモジュールに使用しますapp-routing.module.ts

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

これはあなたのHTMLになります:

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

4

htmlファイル:

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

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

tsファイル:

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

querySelectorはscrolllToというディレクティブに簡単に入れることができます。URLは、<a scrollTo="test1" [routerLink]="['./']"> Go to Test 1 section </a>
John Peters

3

フラグメントプロパティはまだアンカースクロールを提供しないため、この回避策は私にとってはうまくいきました:

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

3

Kalyoyanの回答に加えて、このサブスクリプションはルーターに関連付けられており、ページが完全に更新されるまで有効です。コンポーネントでルーターイベントをサブスクライブする場合は、必ず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();
  }
}

ルーティングイベントのサブスクリプションは自動的に破棄されると思いました。

3

私は自分のウェブサイトでこれを機能させたばかりなので、ここに私の解決策を投稿する価値があると考えました。

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

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

そして、あなたのコンポーネントに、これを含めることを確認してください:

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

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

element.scrollIntoView()or と書くだけの方がいいと思いますelement.scrollIntoView(true)。あなたのバージョンは私のためにコンパイルされませんでした(おそらくstrictNullChecksのためですか?)。
David L.

3

すべてのソリューションを読んだ後、コンポーネントを探したところ、元の質問で要求されていたとおりに機能するコンポーネントを見つけました。https://www.npmjs.com/package/ng2-scroll-to

インストールするときは、次のような構文を使用します。

// 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 {
}

それは私には本当にうまくいきました。


ホイールを使用し、再度発明しないでください;)
Yogen Rai

そのコンポーネントの背後にあるコードを見ましたか?非常に壊れやすいように見えます-プロジェクトには14の未解決の問題があります-これには、要素が存在しない、ターゲットがnull、要素にスクロールしない、ブラウザサポートの問題などが含まれます。
ドレナイ

親コンポーネントに子がある場合(子にアンカーエンティティやアンカー名がある場合)は機能せず、ページを更新するだけです
Sasha Bond

3

クエリパラメータのないページで機能するシンプルなソリューションは、ブラウザのバック/フォワード、ルーター、ディープリンクに準拠しています。

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

タイムアウトは、ページが* ngIfによって「保護された」動的データをロードできるようにするだけです。これは、ルートを変更するときにページの上部にスクロールする場合にも使用できます。デフォルトの上部アンカータグを指定するだけです。


2

これらの要素IDをURLに追加する必要がない場合は、次のリンクを検討することを検討してください。

Angular 2-現在のページの要素へのアンカーリンク

// html
// add (click) event on element
<a (click)="scroll({{any-element-id}})">Scroll</a>

// in ts file, do this
scroll(sectionId) {
let element = document.getElementById(sectionId);

  if(element) {
    element.scrollIntoView(); // scroll to a particular element
  }
 }


1

JavierFuentesの回答を参照する別の回避策を次に示します。

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

スクリプトで:

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

これにより、URLにハッシュタグが含まれているページに直接アクセスした場合に、要素に直接スクロールできます。

しかし、この場合、私はルートフラグメントをサブスクライブしましngAfterViewCheckedngAfterViewChecked()が、毎回継続的に呼び出され、ngDoCheckユーザーが上にスクロールして戻ることができないため、routeFragmentSubscription.unsubscribe、ビューが要素にスクロールされてから0ミリ秒のタイムアウト後に呼び出されます。

さらに gotoHashtag、ユーザーが具体的にアンカータグをクリックしたときに要素にスクロールするメソッドが定義されています。

更新:

URLにクエリ文字列がある場合[routerLink]="['self-route', id]"、アンカーではクエリ文字列は保持されません。私は同じために次の回避策を試しました:

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

1

これは私のために働く!このngForは動的にタグをアンカーするため、レンダリングが完了するまで待つ必要があります

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>

私のtsファイル:

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

それをインポートすることを忘れないでくださいViewChildrenQueryListそしていくつかのコンストラクタを追加してくださいActivatedRoute!!


1

他の回答とは異なり、私はさらに追加focus()scrollIntoView()ます。またsetTimeout、URLを変更すると先頭にジャンプするため、使用しています。その理由はよくわかりませんが、setTimeoutが、回避策はあるです。

原点:

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

先:

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

タイプスクリプト:

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

1

同じ問題がありました。解決策:ビューポートスクローラーを使用する https://angular.io/api/common/ViewportScroller#scrolltoanchor

-app-routing.module.tsコード:

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

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

-コンポーネントHTML

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

-コンポーネントのコード:

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


    export class ParametrageComponent {

      constructor(private viewScroller: ViewportScroller) {}

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

    }

0

nmp- ngx-scroll-toで利用できる非常に便利なプラグインをテストしました。ただし、Angular 4+用に設計されていますが、おそらく誰かがこの答えが役に立つと思うでしょう。


0

私はこれらの解決策のほとんどを試しましたが、それが機能しない別のフラグメントを残して戻ってくると問題が発生したので、100%機能する少し異なることを行い、URLの醜いハッシュを取り除きました。

tl; drこれは、私がこれまで見てきた方法よりも良い方法です。

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();
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.