带有标签的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 的人,请注意,01
或 100
不是有效的 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);
给了我一个无效的选择器错误。你确定这是正确的吗?谢谢
当您再次单击同一链接时,它不起作用。如果用户点击相同的锚链接<a [routerLink]="['/faq']" fragment="section6">
,有人能做到这一点吗?
@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();
不要忘记导入 ViewChildren
、QueryList
等并添加一些构造函数 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路由到页面锚点的主要内容,如果未能解决你的问题,请参考以下文章
使用带有 Angular2 和 Socket.io 的量角器运行 e2e 测试