带有标签的Angular2路由到页面锚点

Posted

技术标签:

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

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

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

等等

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

【问题讨论】:

【参考方案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 你有没有想过这个问题?我遇到了同样的问题。 @Muffin,试试类似github.com/juniormayhe/Mailing/blob/master/Mailing.SPA/src/app/… 这需要更多曝光。这是一个更好的答案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();
    

【讨论】:

以上是关于带有标签的Angular2路由到页面锚点的主要内容,如果未能解决你的问题,请参考以下文章

锚点href vs 角度路由器链接

使用带有 Angular2 和 Socket.io 的量角器运行 e2e 测试

重新路由到带有/用户数据 Angular 2 和 Firebase 的个人资料页面

angular 路由可以跳转到当前页面的某个位置吗

Vue路由器导航或滚动到锚点(#锚点)

Angular2:输入控制带有ngFor的表单标签内的损失值