【问题标题】:Angular2 Routing with Hashtag to page anchor带有标签的Angular2路由到页面锚点
【发布时间】:2016-07-06 05:36:03
【问题描述】:

我希望在我的 Angular2 页面上添加一些链接,当点击这些链接时,将跳转到该页面的特定位置,就像普通主题标签所做的那样。所以链接会是这样的

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

等等

我认为我不需要 HashLocationStrategy,因为我对正常的 Angular2 方式很好,但如果我直接添加,链接实际上会跳转到根目录,而不是同一页面上的某个位置。任何方向表示赞赏,谢谢。

【问题讨论】:

    标签: routing angular hashtag


    【解决方案1】:

    更新

    现在支持此功能

    <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 进行了跟踪

    【讨论】:

    • @invot 一个带有字符串的变量(例如问题中的 123 是什么)假设路由路径需要像 { path: 'users/:id', ....} 这样的参数
    • 如果你想滚动到锚点看这个帖子:github.com/angular/angular/issues/6595
    • 注意:这仍然不滚动
    • 是的,它适用于 setTimeout,如果我找到更好的解决方案,我会告诉你
    • 对于那些难以滚动到类似数字的 ID 的人,请注意,01100 不是有效的 CSS 选择器。您可能想要添加一个字母或其他内容以使其成为有效的选择器。所以你仍然可以将01 作为片段传递,但id 需要类似于d01,因此document.querySelector('#d'+id) 会匹配。
    【解决方案2】:

    抱歉回复晚了; Angular 路由文档中有一个预定义的函数,它可以帮助我们使用主题标签路由到页面锚点,即 anchorScrolling: 'enabled'

    Step-1:-首先在app.module.ts文件中导入RouterModule:-

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

    第 2 步:- 转到 HTML 页面,创建导航并添加两个重要属性,如 [routerLink]fragment匹配各自的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/

    【讨论】:

    • 非常感谢。干净,简洁,它的工作原理!
    • 是的,谢谢,使用 Angular 7 在页面加载时自动 scoll,只需在 anchorScrolling 选项下添加 scrollPositionRestoration: 'enabled', :)
    • 这会将哈希正确地附加到我的 url 的末尾,但它不会锚定到具有相同名称的 div。我不确定我错过了什么。我按照上面的三个步骤。
    • @oaklandrichie:你能在这里分享 jsfiddle / stackblitz 代码吗?我可以帮你
    • 这个答案绝对应该被接受,就像魅力一样!
    【解决方案3】:

    虽然Günter's answer是正确的,并没有涵盖“跳转到”锚标签部分

    因此,另外:

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

    请注意,这是一种解决方法!关注this github issue 以获取未来更新。感谢 Victor Savkin 提供解决方案!

    【讨论】:

    • 您好,我正在制作一个常见问题解答页面,您可以通过单击页面顶部列表中定义的问题跳转到答案。所以用户在跳转到锚点时已经在当前页面。如果我希望 routerLink 属性起作用,我必须将 "['../faq']" 作为值,否则它将尝试跳转到 /faq/faq/#anchor,而不是 /faq/#anchor。这是正确的方法还是有更优雅的方法来引用 routerlink 中的当前页面?此外,document.querySelector("#" + tree.fragment); 给了我一个无效的选择器错误。你确定这是正确的吗?谢谢
    • 当您再次单击同一链接时,它不起作用。如果用户点击相同的锚链接&lt;a [routerLink]="['/faq']" fragment="section6"&gt;,有人能做到这一点吗?
    • @JuniorM 你有没有想过这个问题?我遇到了同样的问题。
    • 这需要更多曝光。这是一个更好的答案IMO。大多数人都想跳到该部分。
    【解决方案4】:

    有点晚了,但这是我发现有效的答案:

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

    确保在组件的开头导入 Router、ActivatedRoute 和 NavigationEnd,一切顺利。

    Source

    【讨论】:

    • 为我工作!如果您想在您已经在的同一页面中导航,请使用 [routerLink]="['.']"
    • 您能进一步解释一下吗?这部分document.querySelector ( "#" + f ) 给了我一个错误,因为它需要一个选择器,而不是一个字符串。
    • @Maurice 对我来说这很有效:element.scrollIntoView()(没有将element 传递给函数)。为了使它顺利,使用这个:element.scrollIntoView({block: "end", behavior: "smooth"}).
    • Intellisense 这里显示在onAnchorClick() 内,我们必须将布尔值传递给scrollIntoView:if (element) { element.scrollIntoView(true); }。现在我可以在同一个链接上单击两次并滚动工作
    【解决方案5】:

    以前的答案都不适合我。在最后的努力中,我尝试了我的模板:

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

    在我的 .ts 中有这个:

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

    它对内部链接按预期工作。这实际上并没有使用锚标记,因此它根本不会触及 URL。

    【讨论】:

    • 简单易行
    【解决方案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 } );
      }
    
    }
    

    【讨论】:

    • 在没有 cmets 或解释的情况下投反对票...这不是一个好习惯...
    • 这对我有用!为什么要使用 ngAfterViewChecked 而不是 ngInit?
    • 感谢@AntoineBoisier-Michaud 的积极反馈。 ngInit 并不总是我需要在我的项目中触发...... ngAfterViewChecked 做到了。你能支持这个解决方案吗?谢谢。
    【解决方案7】:

    如果将这些元素 ID 附加到 url 并不重要,您应该考虑查看此链接:

    Angular 2 - Anchor Links to Element on Current Page

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

    【讨论】:

      【解决方案8】:

      所有其他答案都适用于 Angular 版本

      here's the link to issue

      您需要做的就是使用RouterModule.forRoot 方法的第二个参数选项设置scrollOffset

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

      【讨论】:

      • 这对外部链接有效吗?从另一个网站说,我点击 www.abc.com#sectionToScrollTo
      • anchorScrolling 不起作用,如果您大量使用 *ngIf,因为它会跳转到早期 :-(
      • 我遇到的唯一问题是时间问题——它往往会在某些元素的样式完全渲染之前跳转到锚点,从而导致定位关闭。如果您可以添加延迟会很好:)
      【解决方案9】:

      在 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 的指令中。网址将是 Go to Test 1 section
      【解决方案10】:

      将此用于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">
      

      【讨论】:

        【解决方案11】:

        添加到 Kalyoyan 的 answer,此订阅与路由器绑定,并且将持续到页面完全刷新为止。在组件中订阅路由事件时,一定要在 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();
          }
        }
        

        【讨论】:

        • 我以为路由事件的订阅会被自动取消。
        【解决方案12】:

        由于片段属性仍然不提供锚滚动,这个解决方法对我有用:

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

        【讨论】:

          【解决方案13】:

          我刚刚在我自己的网站上完成了这项工作,所以我认为值得在这里发布我的解决方案。

          <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()element.scrollIntoView(true)。你的版本没有为我编译(可能是因为 strictNullChecks?)。
          【解决方案14】:

          在阅读了所有解决方案后,我寻找了一个组件,并找到了一个完全符合原始问题要求的组件:滚动到锚链接。 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 {
          }
          

          对我来说效果很好。

          【讨论】:

          • 用轮子,不要再发明了;)
          • 您查看过该组件背后的代码吗?它看起来非常脆弱 - 该项目还有 14 个未解决的问题 - 其中包括元素不存在、目标为空、无法滚动到元素、浏览器支持问题。
          • 在父组件中有子项(子项具有锚定实体和/或锚点名称)时不起作用,它只会刷新页面
          【解决方案15】:

          适用于没有任何查询参数的页面的简单解决方案是浏览器后退/前进、路由器和深度链接兼容。

          <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 “保护”的动态数据。这也可以用于在更改路线时滚动到页面顶部 - 只需提供默认的顶部锚标记即可。

          【讨论】:

            【解决方案16】:

            与其他答案不同,我还会添加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();
                    }
                });
            }
            

            【讨论】:

              【解决方案17】:

              我遇到了同样的问题。 解决方案:使用 View port Scroller 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);
                    }
              
                  }
              

              【讨论】:

                【解决方案18】:

                这是另一个参考 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 中有标签的页面。

                但在这种情况下,我在ngAfterViewChecked 中订阅了路由片段,但ngAfterViewChecked() 每次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);
                }
                

                【讨论】:

                  【解决方案19】:

                  这对我有用!这个 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 !!

                  【讨论】:

                    【解决方案20】:

                    我刚刚测试了 nmp 中非常有用的插件 - ngx-scroll-to,它对我来说非常有用。但是它是为 Angular 4+ 设计的,但也许有人会发现这个答案很有帮助。

                    【讨论】:

                      【解决方案21】:

                      我尝试了大多数这些解决方案,但在离开和返回另一个片段时遇到了问题,它不起作用,所以我做了一些不同的事情,它 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();
                          }
                      }
                      

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2011-03-06
                        • 2018-08-05
                        • 1970-01-01
                        • 1970-01-01
                        • 2017-03-29
                        • 1970-01-01
                        • 2019-01-02
                        • 1970-01-01
                        相关资源
                        最近更新 更多