it-swarm-eu.dev

Angular2 Routing con Hashtag per ancorare la pagina

Desidero aggiungere alcuni collegamenti sulla mia pagina Angular2, che quando si fa clic, salterà a posizioni specifiche all'interno di quella pagina, come i normali hashtag. Quindi i collegamenti sarebbero qualcosa di simile

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

eccetera.

Non penso di aver bisogno di HashLocationStrategy, dato che sto bene con il normale modo Angular2, ma se aggiungo direttamente, il link salterà effettivamente alla radice, non da qualche parte sulla stessa pagina. Qualsiasi direzione è apprezzata, grazie.

103
Stone

Update

Questo è ora supportato

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

Aggiungi sotto il codice al tuo componente per scorrere

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

Originale

Questo è un problema noto e tracciato a https://github.com/angular/angular/issues/6595

104

Sebbene la risposta di Günter sia corretta, non copre il "salto" alla parte del tag di ancoraggio .

Pertanto, in aggiunta a:

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

... nel componente (genitore) in cui hai bisogno di un comportamento "salta a", aggiungi:

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

  }
}

Si prega di notare che questa è una soluzione alternativa ! Segui questo problema github per gli aggiornamenti futuri. Crediti a Victor Savkin per aver fornito la soluzione!

48
Kaloyan Kosev

Un po 'tardi ma ecco una risposta che ho trovato che funziona:

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

E nel componente:

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

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

Quanto sopra non scorre automaticamente alla vista se si atterra già su una pagina con un'ancora, quindi ho usato la soluzione sopra in my ngInit in modo che potesse funzionare anche con quella:

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

Assicurati di importare Router, ActivatedRoute e NavigationEnd all'inizio del tuo componente e dovrebbe essere tutto a posto.

Fonte

25
Ali Bari

Ci scusiamo per aver risposto un po 'tardi; C'è una funzione predefinita nella Angular Routing Documentation che ci aiuta nell'instradamento con un hashtag per l'ancoraggio della pagina i.e, anchorScrolling: 'enabled'

Step-1: - Prima importa il RouterModule nel file app.module.ts: -

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

Step-2: - Vai alla pagina HTML, crea la navigazione e aggiungi due attributi importanti come [routerLink] e fragment per la corrispondenza dei rispettivi 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>

Step-3: - Crea una sezione/div facendo corrispondere il nome ID con il frammento : -

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

Come riferimento, ho aggiunto l'esempio seguente creando una piccola demo che aiuta a risolvere il tuo problema.

Demo: https://routing-hashtag-page-anchors.stackblitz.io/

18
Naheed Shareef

Nessuna delle risposte precedenti ha funzionato per me. In un ultimo disperato tentativo, ho provato nel mio modello:

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

Con questo nel mio .ts:

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

E funziona come previsto per i collegamenti interni. Questo in realtà non utilizza i tag di ancoraggio in modo che non tocchi affatto l'URL.

16
Wenqin Chen

Le soluzioni di cui sopra non hanno funzionato per me ... Questo l'ha fatto:

Innanzitutto, prepara MyAppComponent per lo scorrimento automatico in 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 );

              }
            }
          } );
    }

  }

}

Quindi, passa a my-app-route inviando l'hashtag 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 } );
  }

}
6
JavierFuentes

Utilizzare questo per il modulo router in app-routing.module.ts:

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

Questo sarà nel tuo codice HTML:

<a href="#/users/123#userInfo">
4
faizal razak

Tutte le altre risposte funzioneranno su Angular versione <6.1. Ma se hai l'ultima versione allora non avrai bisogno di fare questi brutti hack come Angular ha risolto il problema.

Ecco il link al problema

Tutto quello che devi fare è impostare scrollOffset con l'opzione del secondo argomento del metodoRouterModule.forRoot.

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

Aggiungendo la risposta di Kalyoyan , questa sottoscrizione è legata al router e continuerà fino a quando la pagina non sarà completamente aggiornata. Quando ti abboni agli eventi del router in un componente, assicurati di annullare l'iscrizione in 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
DBosley

Ho appena ottenuto questo lavoro sul mio sito Web, quindi ho pensato che sarebbe valso la pena pubblicare la mia soluzione qui.

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

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

E poi nel tuo componente, assicurati di includere questo:

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

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

Dopo aver letto tutte le soluzioni, ho cercato un componente e ne ho trovato uno che fa esattamente ciò che la domanda originale chiedeva: scorrere verso i collegamenti di ancoraggio. https://www.npmjs.com/package/ng2-scroll-to

Quando lo installi, usi la sintassi come:

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

Ha funzionato davvero bene per me.

3
John

Poiché la proprietà del frammento non fornisce ancora lo scrolling dell'ancora, questa soluzione alternativa ha fatto il trucco per me:

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

Una soluzione semplice che funziona per pagine senza parametri di query, è browser back/forward, router e deep-linking compliant.

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

Il timeout è semplicemente per consentire alla pagina di caricare qualsiasi dato dinamico "protetto" da un * ngIf. Questo può anche essere utilizzato per scorrere fino alla parte superiore della pagina quando si cambia rotta: è sufficiente fornire un tag di ancoraggio superiore predefinito.

2
Graham Hotchkiss

Ecco un'altra soluzione con riferimento alla risposta JavierFuentes:

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

in sceneggiatura:

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

Ciò consente all'utente di scorrere direttamente all'elemento, se l'utente atterra direttamente sulla pagina con hashtag in url.

Ma in questo caso, ho sottoscritto il frammento di route in ngAfterViewChecked ma ngAfterViewChecked() viene chiamato continuamente per ogni ngDoCheck e non consente all'utente di tornare indietro, così routeFragmentSubscription.unsubscribe viene chiamato dopo un timeout di 0 millis dopo che la vista è stata spostata sull'elemento.

Inoltre, il metodo gotoHashtag è definito per scorrere fino all'elemento quando l'utente fa clic in modo specifico sul tag di ancoraggio.

Aggiornamento:

Se url ha querystrings, [routerLink]="['self-route', id]" in anchor non conserverà le querystring. Ho provato a seguire la soluzione alternativa per lo stesso:

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

Questo lavoro per me !! Questo ngPer così dinamicamente ancora il tag, è necessario attendere il rendering

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>

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

Non dimenticare di importare ViewChildren, QueryList ecc. E aggiungere qualche costruttore ActivatedRoute !!

1
John Connor

Ho avuto lo stesso problema. La soluzione: utilizzando View port Scroller https://angular.io/api/common/ViewportScroller#scrolltoanchor

- codice app-routing.module.ts:

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

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

- HTML componente

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

- Codice del componente:

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


    export class ParametrageComponent {

      constructor(private viewScroller: ViewportScroller) {}

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

    }
1
Abderrahim Taleb

A differenza di altre risposte, aggiungerei anche focus() insieme a scrollIntoView(). Inoltre sto usando setTimeout dato che salta in alto altrimenti quando si cambia l'URL. Non sono sicuro di quale sia stata la ragione, ma sembra che setTimeout risolva il problema.

Origine:

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

Destinazione:

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

Dattiloscritto:

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

Ho provato la maggior parte di queste soluzioni, ma ho riscontrato problemi nell'andare e tornare con un altro frammento che non avrebbe funzionato, quindi ho fatto qualcosa di un po 'diverso che funziona al 100% e mi sbarazza del brutto hash nell'URL.

tl; dr, ecco un modo migliore di quello che ho visto finora.

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

Ho appena testato plugin molto utili disponibili in nmp - ngx-scroll-to , che funziona benissimo per me. Tuttavia è progettato per Angular 4+, ma forse qualcuno troverà utile questa risposta.

0
mpro