web-dev-qa-db-fra.com

Routage Angular2 avec hashtag vers un ancre de page

Je souhaite ajouter quelques liens sur ma page Angular2 qui, lorsque vous cliquez sur ce bouton, accéderont à des positions spécifiques à l'intérieur de cette page, comme ce que font les hashtags normaux. Donc, les liens seraient quelque chose comme

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

etc.

Je ne pense pas que j'ai besoin de HashLocationStrategy, car je suis d'accord avec la méthode normale Angular2, mais si j'ajoute directement, le lien sautera en fait à la racine, pas quelque part sur la même page. Toute direction est appréciée, merci.

105
Stone

Mettre à jour

Ceci est maintenant supporté

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

Ajoutez le code ci-dessous à votre composant pour faire défiler

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

Original

Il s’agit d’un problème connu qui a fait l’objet d’un suivi à l’adresse https://github.com/angular/angular/issues/6595

105
Günter Zöchbauer

Bien que la réponse de Günter soit correct, il ne couvre pas le "saut à" la partie de balise d'ancrage .

Par conséquent, en plus de:

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

... dans le composant (parent) où vous avez besoin d'un comportement de "saut vers", ajoutez:

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

  }
}

Veuillez noter qu'il s'agit d'une solution de contournement ! Suivez ce numéro de github pour les futures mises à jour. Crédits à Victor Savkin pour avoir fourni la solution!

48
Kaloyan Kosev

Désolé de répondre un peu en retard; Il existe une fonction prédéfinie dans la documentation de routage Angular qui nous aide à acheminer un hashtag vers une ancre de page, c'est-à-dire anchorScrolling: 'enabled'

Étape 1: - Importez d'abord le RouterModule dans le fichier app.module.ts: -

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

Étape 2: - Accédez à la page HTML, créez la navigation et ajoutez deux attributs importants, tels que [routeurLink] et fragment, pour faire correspondre les noms respectifs. Div ID's: -

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

Étape 3: - Créez une section/div en faisant correspondre le nom de l'ID au fragment: -

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

Pour votre référence, j'ai ajouté l'exemple ci-dessous en créant une petite démo qui vous aidera à résoudre votre problème.

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

25
Naheed Shareef

Un peu tard mais voici une réponse que j'ai trouvée qui fonctionne:

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

Et dans le composant:

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

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

Ce qui précède ne défile pas automatiquement vers la vue si vous atterrissez déjà sur une page avec une ancre. J'ai donc utilisé la solution ci-dessus dans mon ngInit pour qu'il puisse également fonctionner avec cela:

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

Assurez-vous d’importer Router, ActivatedRoute et NavigationEnd au début de votre composant et tout devrait être parfait.

Source

25
Ali Bari

Aucune des réponses précédentes n'a fonctionné pour moi. Dans un dernier effort, j'ai essayé dans mon modèle:

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

Avec ça dans mes .ts:

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

Et cela fonctionne comme prévu pour les liens internes. Cela n'utilise pas réellement les balises d'ancrage, donc cela ne toucherait pas du tout l'URL.

18
Wenqin Chen

Les solutions ci-dessus n'ont pas fonctionné pour moi ... Celui-ci l'a fait:

Commencez par préparer MyAppComponent pour le défilement automatique dans 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 );

              }
            }
          } );
    }

  }

}

Ensuite, accédez à my-app-route envoi de prodID hashtag

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

Toutes les autres réponses fonctionneront sur Angular version <6.1. Mais si vous avez la dernière version, vous n'aurez pas besoin de faire ces hacks laids car Angular a corrigé le problème.

voici le lien pour émettre

Tout ce que vous devez faire est de définir scrollOffset avec l'option du deuxième argument de la méthodeRouterModule.forRoot.

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

Utilisez ceci pour le module de routeur dans app-routing.module.ts:

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

Ce sera dans votre code HTML:

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

Ajoutant à la réponse de Kalyoyan , cet abonnement est lié au routeur et restera actif jusqu'à ce que la page soit entièrement actualisée. Lorsque vous vous abonnez à des événements de routeur dans un composant, veillez à vous désabonner dans 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

Je viens de travailler sur mon propre site Web, alors j’ai pensé que cela valait la peine d’afficher ma solution ici.

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

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

Et puis dans votre composant, assurez-vous d'inclure ceci:

 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

Comme la propriété fragment ne fournit toujours pas le défilement des ancres, cette solution de contournement a fait l'affaire pour moi:

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

Après avoir lu toutes les solutions, j'ai cherché un composant et j'en ai trouvé un qui répond exactement à la question initiale: faire défiler les liens vers les liens d'ancrage. https://www.npmjs.com/package/ng2-scroll-to

Lorsque vous l'installez, vous utilisez une syntaxe comme celle-ci:

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

Cela a très bien fonctionné pour moi.

3
John

Une solution simple qui fonctionne pour les pages sans aucun paramètre de requête, est la navigation en arrière/en aval, conforme au routeur et à la liaison en profondeur.

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

Le délai est simplement pour permettre à la page de charger toutes les données dynamiques "protégées" par un * ngIf. Cela peut également être utilisé pour faire défiler le haut de la page lors du changement de route - il suffit de fournir une balise d'ancrage supérieure par défaut.

2
Graham Hotchkiss

Voici une autre solution de contournement en référence à la réponse de JavierFuentes:

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

en script:

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

Cela permet à l'utilisateur de faire défiler directement l'élément, s'il atterrit directement sur la page comportant un hashtag dans l'URL.

Mais dans ce cas, j'ai souscrit un fragment de route dans ngAfterViewChecked mais ngAfterViewChecked() est appelé en permanence chaque ngDoCheck et ne permet pas à l'utilisateur de faire défiler vers le haut, donc routeFragmentSubscription.unsubscribe est appelé après un délai d'attente de 0 millis après que la vue est passée à l'élément.

De plus, la méthode gotoHashtag est définie pour faire défiler l'élément jusqu'à ce que l'utilisateur clique spécifiquement sur la balise d'ancrage.

Mise à jour:

Si url a des chaînes de requête, [routerLink]="['self-route', id]" dans l'ancre ne conservera pas les chaînes de requête. J'ai essayé la solution suivante pour la même chose:

<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

Celui-ci fonctionne pour moi !! Ce ngFor donc il dynamiquement ancrer tag, Vous devez attendre leur rendre

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>

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

N'oubliez pas d'importer cette ViewChildren, QueryList etc .. et d'ajouter du constructeur ActivatedRoute !!

1
John Connor

J'ai eu le même problème. La solution: utiliser View port Scroller https://angular.io/api/common/ViewportScroller#scrolltoanchor

- Code app-routage.module.ts:

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

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

- Composant HTML

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

- Code du composant:

    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

Contrairement à d’autres réponses, j’ajouterais aussi focus() avec scrollIntoView(). De plus, j'utilise setTimeout car il saute au sommet lorsque vous modifiez l'URL. Je ne sais pas quelle en était la raison, mais il semble que la solution de contournement soit setTimeout.

Origine:

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

Destination:

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

Manuscrit:

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

J'ai essayé la plupart de ces solutions, mais j'ai eu du mal à quitter et à revenir avec un autre fragment qui ne fonctionnerait pas. J'ai donc fait quelque chose d'un peu différent, qui fonctionne à 100% et qui supprime le vilain hachage de l'URL.

voici un meilleur moyen que ce que j'ai vu jusqu'à présent.

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

Je viens de tester un plugin très utile disponible dans nmp - ngx-scroll-to , ce qui fonctionne très bien pour moi. Cependant, il est conçu pour Angular 4+, mais quelqu'un trouvera peut-être cette réponse utile.

0
mpro