Маршрутизация Angular2 с хэштегом для привязки к странице


120

Я хочу добавить несколько ссылок на мою страницу Angular2, которые при нажатии будут переходить на определенные позиции на этой странице, как это делают обычные хэштеги. Таким образом, ссылки будут примерно такими:

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

и т.п.

Я не думаю, что мне нужен HashLocationStrategy, поскольку меня устраивает обычный способ Angular2, но если я добавлю напрямую, ссылка фактически перейдет в корень, а не где-то на той же странице. Любое направление приветствуется, спасибо.

Ответы:


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', ....}
Günter Zöchbauer

2
Если вы хотите прокрутить до якоря, посмотрите этот пост: github.com/angular/angular/issues/6595
Pere Pages

12
Примечание: это все еще не прокручивается
Мартин Колл,

2
да, он работает с setTimeout, если я
Амр Ибрагим

1
Для тех, кто изо всех сил пытается прокрутить до числовых идентификаторов, помните, что это 01или 100недействительные селекторы CSS. Возможно, вы захотите добавить букву или что-то еще, чтобы сделать его допустимым селектором. Таким образом, вы все равно будете проходить 01как фрагмент, но idдолжно быть что-то вроде d01и, следовательно, document.querySelector('#d'+id)будет соответствовать.
pop

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 для будущих обновлений. Благодарим Виктора Савкина за решение проблемы!


привет, я делаю страницу часто задаваемых вопросов, где вы можете перейти к ответу, щелкнув вопрос, определенный в списке вверху страницы. Таким образом, пользователь уже находится на текущей странице, когда он переходит к привязке. Если я хочу, чтобы атрибут routerLink работал, я должен указать его в "['../faq']"качестве значения, иначе он попытается перейти к привязке / faq / faq / # вместе с привязкой / faq / #. Это правильный способ сделать это или есть более элегантный способ обратиться к текущей странице в routerlink? Кроме того, document.querySelector("#" + tree.fragment);дает мне недопустимую ошибку селектора. Вы уверены, что это правильно? Спасибо
Морис

2
при повторном нажатии на ту же ссылку это не работает. Кто-нибудь делал это, если пользователь нажимал на ту же ссылку привязки <a [routerLink]="['/faq']" fragment="section6">?
Джуниор Майе

@JuniorM Вы когда-нибудь в этом разбирались? У меня та же проблема.
The Muffin Man

@Muffin, попробуйте что-нибудь вроде этого github.com/juniormayhe/Mailing/blob/master/Mailing.SPA/src/app/…
Junior Mayhé

1
Это требует большего воздействия. Это лучший ответ ИМО. Большинство людей захотят перейти в раздел.
iamtravisw

45

Извините за то, что ответил немного поздно; В документации по маршрутизации Angular есть предопределенная функция, которая помогает нам в маршрутизации с помощью хэштега для привязки к странице, то есть anchorScrolling: 'enabled'

Шаг 1: - Сначала импортируйте RouterModule в файл app.module.ts: -

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

Шаг 2: - Перейдите на HTML-страницу, создайте навигацию и добавьте два важных атрибута, таких как [routerLink] и фрагмент, для сопоставления соответствующих идентификаторов 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: - Создайте раздел / 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

2
Да, спасибо, за автоматическую прокрутку при загрузке страницы с помощью Angular 7, просто нужно добавить scrollPositionRestoration: 'enabled',опцию anchorScrolling :)
Mickaël

Это правильно добавляет хеш в конец моего URL-адреса, но он не привязан к div с тем же именем. Я не уверен, что мне не хватает. Я выполнил три шага выше.
oaklandrichie

@oaklandrichie: Не могли бы вы поделиться здесь кодом jsfiddle / stackblitz. Я могу помочь вам
Нахид Шариф

этот ответ определенно следует принять, работает как шарм!
Поцелуй Коппани

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

Обязательно импортируйте Router, ActivatedRoute и NavigationEnd в начало вашего компонента, и все должно быть в порядке.

Источник


4
Работает для меня! Если вы хотите перемещаться по той же странице, на которой вы уже находитесь, используйте [routerLink] = "['.']"
Рауль

1
не могли бы вы объяснить дальше? эта часть document.querySelector ( "#" + f )дает мне ошибку, потому что ожидает селектор, а не строку.
Морис

1
@Maurice для меня это работает: element.scrollIntoView()(без перехода elementк функции). Для того, чтобы сделать ее гладкой, используйте это: element.scrollIntoView({block: "end", behavior: "smooth"}).
Mr. B.

Intellisense здесь показывает , что внутри onAnchorClick(), мы должны пройти булевы к scrollIntoView: if (element) { element.scrollIntoView(true); }. Теперь я могу дважды щелкнуть по той же ссылке и пролистать работы
Джуниор Мэйхе

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

Вышеупомянутые решения не помогли мне ... Вот этот:

Во-первых, подготовьтесь MyAppComponentк автоматической прокрутке в 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 );

              }
            }
          } );
    }

  }

}

Затем перейдите к 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

Это сработало для меня! Но зачем использовать ngAfterViewChecked вместо ngInit?
Антуан Буазье-Мишо

Спасибо @ AntoineBoisier-Michaud за положительный отзыв. ngInit не срабатывает всегда, когда мне нужно в моем проекте ... ngAfterViewChecked делает это. Не могли бы вы проголосовать за это решение, пожалуйста? Спасибо.
JavierFuentes

6

Все остальные ответы будут работать на Angular версии <6.1. Но если у вас последняя версия, вам не нужно делать эти уродливые хаки, поскольку Angular устранил проблему.

вот ссылка на выпуск

Все, что вам нужно сделать, это установить scrollOffset с помощью второго аргумента 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

anchorScrolling не работает, если вы широко используете * ngIf, потому что он перескакивает на ранний
этап

Единственная проблема, с которой я столкнулся, - это время - он имеет тенденцию переходить на якорь до того, как стили некоторых элементов достигают полной визуализации, что приводит к отключению позиционирования. Было бы неплохо, если бы вы могли добавить задержку :)
Чарли

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 visible="['./'ght"> Перейти к разделу Test 1 </a>
Джон Питерс,

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()или element.scrollIntoView(true). Ваша версия у меня не компилировалась (может быть, из-за strictNullChecks?).
Дэвид Л.

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

У меня это сработало очень хорошо.


Используйте колесо, не изобретайте его снова;)
Йоген Рай

Вы смотрели код этого компонента? Это выглядит очень хрупким - у проекта также есть 14 открытых проблем, в том числе такие вещи, как «элемент не существует», таргетинг на null, отсутствие прокрутки до элемента, проблемы с поддержкой браузера.
Дренай,

не работает, когда у вас есть дочерний элемент (дочерний элемент имеет привязанные сущности и / или имена привязок) в родительском компоненте, он просто обновляет страницу
Саша Бонд

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

если не имеет значения добавление этих идентификаторов элементов к 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-адресе.

Но в этом случае я подписал фрагмент маршрута, 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);
}

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

Не забудьте импортировать это ViewChildrenи QueryListт.д. и добавить конструктор 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

Я была такая же проблема. Решение: используя 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);
      }

    }

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();
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.