it-swarm.com.de

Winkel 2 Blättern Sie bei Routenänderung nach oben

Wenn ich in meiner App Angular 2 eine Seite nach unten scrolle und auf den Link unten auf der Seite klicke, ändert sich die Route und die nächste Seite wird angezeigt, aber es wird nicht an den Anfang der Seite geblättert. Wenn daher die erste Seite lang ist und die zweite Seite nur wenige Inhalte hat, wird der Eindruck erweckt, dass der zweiten Seite der Inhalt fehlt. Da der Inhalt nur sichtbar ist, wenn der Benutzer an den Anfang der Seite blättert.

Ich kann das Fenster in ngInit der Komponente an den Anfang der Seite scrollen. Gibt es eine bessere Lösung, mit der alle Routen in meiner App automatisch verarbeitet werden können?

163
Naveed Ahmed

Sie können einen Listenänderungslistener in Ihrer Hauptkomponente registrieren und bei Routenänderungen nach oben scrollen.

import { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';

@Component({
    selector: 'my-app',
    template: '<ng-content></ng-content>',
})
export class MyAppComponent implements OnInit {
    constructor(private router: Router) { }

    ngOnInit() {
        this.router.events.subscribe((evt) => {
            if (!(evt instanceof NavigationEnd)) {
                return;
            }
            window.scrollTo(0, 0)
        });
    }
}
275

Angular 6.1 und höher:

Angular 6.1 (veröffentlicht am 25.08.2018) fügte über eine Funktion namens "Router Scroll Position Restoration" eine integrierte Unterstützung hinzu, um dieses Problem zu lösen. Wie im offiziellen Angular Blog beschrieben, müssen Sie dies nur in der Routerkonfiguration wie folgt aktivieren:

RouterModule.forRoot(routes, {scrollPositionRestoration: 'enabled'})

Darüber hinaus heißt es im Blog: "Es wird erwartet, dass dies in einer zukünftigen Hauptversion zum Standard wird". Es ist daher wahrscheinlich, dass Sie ab Angular 7 in Ihrem Code nichts weiter tun müssen, und dies funktioniert einfach richtig aus der Box.

Angular 6.0 und früher:

Die hervorragende Antwort von @ GuilhermeMeireles behebt zwar das ursprüngliche Problem, führt jedoch ein neues ein, indem das normale Verhalten gebrochen wird, das Sie beim Navigieren vor oder zurück (mit den Browserschaltflächen oder über die Position im Code) erwarten. Das erwartete Verhalten ist, dass wenn Sie zur Seite zurück navigieren, diese an derselben Stelle nach unten scrollen sollte, als Sie auf den Link geklickt haben. Wenn Sie jedoch auf jede Seite gelangen, bricht ein Bildlauf nach oben, was diese Erwartung offensichtlich bricht. 

Mit dem folgenden Code wird die Logik zum Erkennen dieser Art von Navigation erweitert, indem die Location PopStateEvent-Sequenz abonniert und die Scroll-to-Top-Logik übersprungen wird, wenn die neu angekommene Seite ein Ergebnis eines solchen Ereignisses ist.

Wenn die Seite, von der aus Sie zurück navigieren, lang genug ist, um das gesamte Ansichtsfenster abzudecken, wird die Bildlaufposition automatisch wiederhergestellt. Wie @JordanNelson jedoch richtig darauf hinweist, müssen Sie bei einer kürzeren Seite die ursprüngliche y-Bildlaufposition verfolgen und wiederherstellen explizit, wenn Sie zur Seite zurückkehren. Die aktualisierte Version des Codes deckt auch diesen Fall ab, indem die Bildlaufposition immer explizit wiederhergestellt wird.

import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
import { Location, PopStateEvent } from "@angular/common";

@Component({
    selector: 'my-app',
    template: '<ng-content></ng-content>',
})
export class MyAppComponent implements OnInit {

    private lastPoppedUrl: string;
    private yScrollStack: number[] = [];

    constructor(private router: Router, private location: Location) { }

    ngOnInit() {
        this.location.subscribe((ev:PopStateEvent) => {
            this.lastPoppedUrl = ev.url;
        });
        this.router.events.subscribe((ev:any) => {
            if (ev instanceof NavigationStart) {
                if (ev.url != this.lastPoppedUrl)
                    this.yScrollStack.Push(window.scrollY);
            } else if (ev instanceof NavigationEnd) {
                if (ev.url == this.lastPoppedUrl) {
                    this.lastPoppedUrl = undefined;
                    window.scrollTo(0, this.yScrollStack.pop());
                } else
                    window.scrollTo(0, 0);
            }
        });
    }
}
153

In Angular 6.1 können Sie nun den Ärger vermeiden und extraOptions als zweiten Parameter an Ihre RouterModule.forRoot() übergeben und scrollPositionRestoration: enabled angeben, damit Angular bei Änderungen der Route nach oben scrollen soll.

Standardmäßig finden Sie dies in app-routing.module.ts:

const routes: Routes = [
  {
    path: '...'
    component: ...
  },
  ...
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      scrollPositionRestoration: 'enabled', // Add options right here
    })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Angular Official Docs

35
Abdul Rafay

Sie können dies präziser schreiben, indem Sie die beobachtbare filter-Methode nutzen:

this.router.events.filter(event => event instanceof NavigationEnd).subscribe(() => {
      this.window.scrollTo(0, 0);
});

Wenn bei der Verwendung von Angular Material 2 sidenav Probleme beim Bildlauf nach oben auftreten, kann dies hilfreich sein. Der Fenster- oder Dokumentenkörper verfügt nicht über die Bildlaufleiste. Sie müssen also den Inhaltscontainer sidenav abrufen und das Element scrollen. Andernfalls versuchen Sie, das Fenster standardmäßig zu scrollen.

this.router.events.filter(event => event instanceof NavigationEnd)
  .subscribe(() => {
      const contentContainer = document.querySelector('.mat-sidenav-content') || this.window;
      contentContainer.scrollTo(0, 0);
});

Das Angular CDK v6.x verfügt jetzt über ein scrolling-Paket , das beim Scrollen helfen kann.

25
mtpultz

Wenn Sie serverseitig gerendert werden, sollten Sie den Code nicht mit windows auf dem Server ausführen, auf dem diese Variable nicht vorhanden ist. Dies würde zum Brechen von Code führen.

export class AppComponent implements OnInit {
  routerSubscription: Subscription;

  constructor(private router: Router,
              @Inject(PLATFORM_ID) private platformId: any) {}

  ngOnInit() {
    if (isPlatformBrowser(this.platformId)) {
      this.routerSubscription = this.router.events
        .filter(event => event instanceof NavigationEnd)
        .subscribe(event => {
          window.scrollTo(0, 0);
        });
    }
  }

  ngOnDestroy() {
    this.routerSubscription.unsubscribe();
  }
}

isPlatformBrowser ist eine Funktion, mit der überprüft wird, ob die aktuelle Plattform, auf der die App gerendert wird, ein Browser ist oder nicht. Wir geben ihm die injizierte platformId.

Es kann auch geprüft werden, ob die Variable windows vorhanden ist, um sicher zu sein, wie folgt:

if (typeof window != 'undefined')
16
Raptor

mach es einfach mit Klick-Aktion

verweisen Sie in Ihrer Hauptkomponente auf HTML #scrollContainer

<div class="main-container" #scrollContainer>
    <router-outlet (activate)="onActivate($event, scrollContainer)"></router-outlet>
</div>

in der Hauptkomponente .ts

onActivate(e, scrollContainer) {
    scrollContainer.scrollTop = 0;
}
12
SAT

Die beste Antwort findet sich in der Angular GitHub-Diskussion ( Beim Ändern der Route wird auf der neuen Seite nicht nach oben gewechselt ).

Vielleicht möchten Sie nur bei Root-Router-Änderungen nach oben wechseln (nicht bei Kindern...., Da Sie Routen mit Lazy Load in f.e. ein Tabset laden können.)

app.component.html

<router-outlet (deactivate)="onDeactivate()"></router-outlet>

app.component.ts

onDeactivate() {
  document.body.scrollTop = 0;
  // Alternatively, you can scroll to top by using this other call:
  // window.scrollTo(0, 0)
}

Volle Kredite an JoniJnm ( ursprünglicher Beitrag )

10
zurfyx

Sie können den AfterViewInit lifecycle-Hook zu Ihrer Komponente hinzufügen.

ngAfterViewInit() {
   window.scrollTo(0, 0);
}
6
stillatmylinux

Hier ist eine Lösung, die ich mir ausgedacht habe. Ich habe die LocationStrategy mit den Router-Ereignissen gekoppelt. Verwenden von LocationStrategy zum Festlegen eines Booleschen Werts, um zu erfahren, wann ein Benutzer den Browserverlauf aktuell durchläuft. Auf diese Weise muss ich nicht eine Reihe von URL- und Y-Scroll-Daten speichern (was ohnehin nicht gut funktioniert, da alle Daten basierend auf der URL ersetzt werden). Dies löst auch den Edge-Fall, wenn ein Benutzer die Zurück- oder Vorwärts-Taste eines Browsers gedrückt hält und mehrere Seiten vor- oder zurückblättert und nicht nur eine.

P.S. Ich habe nur die neueste Version von IE, Chrome, FireFox, Safari und Opera getestet (ab diesem Post).

Hoffe das hilft.

export class AppComponent implements OnInit {
  isPopState = false;

  constructor(private router: Router, private locStrat: LocationStrategy) { }

  ngOnInit(): void {
    this.locStrat.onPopState(() => {
      this.isPopState = true;
    });

    this.router.events.subscribe(event => {
      // Scroll to top if accessing a page, not via browser history stack
      if (event instanceof NavigationEnd && !this.isPopState) {
        window.scrollTo(0, 0);
        this.isPopState = false;
      }

      // Ensures that isPopState is reset
      if (event instanceof NavigationEnd) {
        this.isPopState = false;
      }
    });
  }
}
4
Sal_Vader_808

Ab Angular 6.1 bietet der Router eine Konfigurationsoption mit dem Namen scrollPositionRestoration an, die auf dieses Szenario zugeschnitten ist.

imports: [
  RouterModule.forRoot(routes, {
    scrollPositionRestoration: 'enabled'
  }),
  ...
]
3
Marty A

Wenn Sie einfach die Seite nach oben scrollen möchten, können Sie dies tun (nicht die beste Lösung, aber schnell)

document.getElementById('elementId').scrollTop = 0;
3
Aliaksei

Diese Lösung basiert auf der @ FernandoEcheverria- und @ GuilhermeMeireles-Lösung, ist jedoch übersichtlicher und arbeitet mit den Popstate-Mechanismen, die der Angular Router bietet. Dies ermöglicht das Speichern und Wiederherstellen der Bildlaufebene mehrerer aufeinanderfolgender Navigationen.

Wir speichern die Bildlaufpositionen für jeden Navigationszustand in einer Karte scrollLevels. Wenn ein Popstate-Ereignis vorliegt, wird die ID des Status, der wiederhergestellt werden soll, vom Angular Router bereitgestellt: event.restoredState.navigationId. Dies wird dann verwendet, um die letzte Bildlaufebene dieses Status von scrollLevels abzurufen.

Wenn für die Route keine Bildlaufebene gespeichert ist, wird sie wie erwartet nach oben gescrollt.

import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';

@Component({
    selector: 'my-app',
    template: '<ng-content></ng-content>',
})
export class AppComponent implements OnInit {

  constructor(private router: Router) { }

  ngOnInit() {
    const scrollLevels: { [navigationId: number]: number } = {};
    let lastId = 0;
    let restoredId: number;

    this.router.events.subscribe((event: Event) => {

      if (event instanceof NavigationStart) {
        scrollLevels[lastId] = window.scrollY;
        lastId = event.id;
        restoredId = event.restoredState ? event.restoredState.navigationId : undefined;
      }

      if (event instanceof NavigationEnd) {
        if (restoredId) {
          // Optional: Wrap a timeout around the next line to wait for
          // the component to finish loading
          window.scrollTo(0, scrollLevels[restoredId] || 0);
        } else {
          window.scrollTo(0, 0);
        }
      }

    });
  }

}
3
Simon Mathewson

Angular hat kürzlich eine neue Funktion eingeführt, mit der Sie im Routing-Modul angular die folgenden Änderungen vornehmen können

@NgModule({
  imports: [RouterModule.forRoot(routes,{
    scrollPositionRestoration: 'top'
  })],
1
Pran R.V

Hallo Leute, das funktioniert für mich in Winkel 4. Sie müssen nur das Elternteil referenzieren, um auf Routerwechsel zu scrollen 

layout.component.pug

.wrapper(#outlet="")
    router-outlet((activate)='routerActivate($event,outlet)')

layout.component.ts

 public routerActivate(event,outlet){
    outlet.scrollTop = 0;
 }`
1
Jonas Arguelles

für iPhone/Ios-Safari können Sie einen setTimeout verwenden

setTimeout(function(){
    window.scrollTo(0, 1);
}, 0);
0
tubbsy

Die Verwendung von Router selbst führt zu Problemen, die Sie nicht vollständig überwinden können, um ein konsistentes Browsererlebnis zu gewährleisten. Meiner Meinung nach ist die beste Methode, einfach ein benutzerdefiniertes directive zu verwenden und die Bildlaufleiste beim Klicken zurücksetzen zu lassen. Das Gute daran ist, dass, wenn Sie sich auf derselben url-Position befinden, auf die Sie klicken, auch die Seite nach oben scrollen wird. Dies stimmt mit normalen Websites überein. Das grundlegende directive könnte ungefähr so ​​aussehen:

import {Directive, HostListener} from '@angular/core';

@Directive({
    selector: '[linkToTop]'
})
export class LinkToTopDirective {

    @HostListener('click')
    onClick(): void {
        window.scrollTo(0, 0);
    }
}

Mit der folgenden Verwendung:

<a routerLink="/" linkToTop></a>

Für die meisten Anwendungsfälle reicht das aus, aber ich kann mir ein paar Probleme vorstellen, die sich daraus ergeben können:

  • Funktioniert nicht mit universal, da window verwendet wird.
  • Kleine Geschwindigkeitseinflüsse auf die Änderungserkennung, da diese durch jeden Klick ausgelöst wird
  • Keine Möglichkeit, diese Direktive zu deaktivieren

Es ist eigentlich ganz einfach, diese Probleme zu überwinden:

@Directive({
  selector: '[linkToTop]'
})
export class LinkToTopDirective implements OnInit, OnDestroy {

  @Input()
  set linkToTop(active: string | boolean) {
    this.active = typeof active === 'string' ? active.length === 0 : active;
  }

  private active: boolean = true;

  private onClick: EventListener = (event: MouseEvent) => {
    if (this.active) {
      window.scrollTo(0, 0);
    }
  };

  constructor(@Inject(PLATFORM_ID) private readonly platformId: Object,
              private readonly elementRef: ElementRef,
              private readonly ngZone: NgZone
  ) {}

  ngOnDestroy(): void {
    if (isPlatformBrowser(this.platformId)) {
      this.elementRef.nativeElement.removeEventListener('click', this.onClick, false);
    }
  }

  ngOnInit(): void {
    if (isPlatformBrowser(this.platformId)) {
      this.ngZone.runOutsideAngular(() => 
        this.elementRef.nativeElement.addEventListener('click', this.onClick, false)
      );
    }
  }
}

Dies berücksichtigt die meisten Anwendungsfälle, mit derselben Verwendung wie die grundlegende, mit dem Vorteil, dass sie aktiviert/deaktiviert wird:

<a routerLink="/" linkToTop></a> <!-- always active -->
<a routerLink="/" [linkToTop]="isActive"> <!-- active when `isActive` is true -->

Werbung, nicht lesen, wenn Sie nicht beworben werden möchten

Eine weitere Verbesserung könnte vorgenommen werden, um zu prüfen, ob der Browser passive-Ereignisse unterstützt. Dies wird den Code etwas komplizierter machen und ist etwas unklar, wenn Sie all dies in Ihren benutzerdefinierten Anweisungen/Vorlagen implementieren möchten. Deshalb habe ich eine kleine library geschrieben, mit der Sie diese Probleme angehen können. Um dieselbe Funktionalität wie oben zu haben, und mit dem hinzugefügten passive-Ereignis, können Sie Ihre Anweisung in diese ändern, wenn Sie die ng-event-options-Bibliothek verwenden. Die Logik befindet sich im Listener click.pnb:

@Directive({
    selector: '[linkToTop]'
})
export class LinkToTopDirective {

    @Input()
    set linkToTop(active: string|boolean) {
        this.active = typeof active === 'string' ? active.length === 0 : active;
    }

    private active: boolean = true;

    @HostListener('click.pnb')
    onClick(): void {
      if (this.active) {
        window.scrollTo(0, 0);
      }        
    }
}
0
PierreDuc

Zusätzlich zu der perfekten Antwort von @Guilherme Meireles , wie unten gezeigt, können Sie Ihre Implementierung optimieren, indem Sie einen glatten Bildlauf wie unten gezeigt hinzufügen

 import { Component, OnInit } from '@angular/core';
    import { Router, NavigationEnd } from '@angular/router';

    @Component({
        selector: 'my-app',
        template: '<ng-content></ng-content>',
    })
    export class MyAppComponent implements OnInit {
        constructor(private router: Router) { }

        ngOnInit() {
            this.router.events.subscribe((evt) => {
                if (!(evt instanceof NavigationEnd)) {
                    return;
                }
                window.scrollTo(0, 0)
            });
        }
    }

fügen Sie dann das folgende Snippet hinzu

 html {
      scroll-behavior: smooth;
    }

in deine styles.css

0

Die Hauptidee hinter diesem Code ist, alle besuchten URLs zusammen mit den jeweiligen scrollY-Daten in einem Array zu behalten. Jedes Mal, wenn ein Benutzer eine Seite (NavigationStart) verlässt, wird dieses Array aktualisiert. Jedes Mal, wenn ein Benutzer eine neue Seite (NavigationEnd) aufruft, entscheiden wir uns, die Y-Position wiederherzustellen oder nicht, je nachdem, wie wir zu dieser Seite gelangen. Wenn eine Seite auf einer Seite verwendet wurde, scrollen wir zu 0. Wenn Browser-Funktionen verwendet wurden, blättern wir zu Y, das in unserem Array gespeichert ist. Entschuldigung für mein Englisch :)

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Location, PopStateEvent } from '@angular/common';
import { Router, Route, RouterLink, NavigationStart, NavigationEnd, 
    RouterEvent } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';

@Component({
  selector: 'my-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {

  private _subscription: Subscription;
  private _scrollHistory: { url: string, y: number }[] = [];
  private _useHistory = false;

  constructor(
    private _router: Router,
    private _location: Location) {
  }

  public ngOnInit() {

    this._subscription = this._router.events.subscribe((event: any) => 
    {
      if (event instanceof NavigationStart) {
        const currentUrl = (this._location.path() !== '') 
           this._location.path() : '/';
        const item = this._scrollHistory.find(x => x.url === currentUrl);
        if (item) {
          item.y = window.scrollY;
        } else {
          this._scrollHistory.Push({ url: currentUrl, y: window.scrollY });
        }
        return;
      }
      if (event instanceof NavigationEnd) {
        if (this._useHistory) {
          this._useHistory = false;
          window.scrollTo(0, this._scrollHistory.find(x => x.url === 
          event.url).y);
        } else {
          window.scrollTo(0, 0);
        }
      }
    });

    this._subscription.add(this._location.subscribe((event: PopStateEvent) 
      => { this._useHistory = true;
    }));
  }

  public ngOnDestroy(): void {
    this._subscription.unsubscribe();
  }
}
0

window.scrollTo() funktioniert für mich in Angular 5 nicht, daher habe ich document.body.scrollTop wie verwendet:

this.router.events.subscribe((evt) => {
   if (evt instanceof NavigationEnd) {
      document.body.scrollTop = 0;
   }
});
0
Rohan Kumar

Dies funktionierte für mich am besten für alle Navigationsänderungen einschließlich der Hash-Navigation 

constructor(private route: ActivatedRoute) {}

ngOnInit() {
  this._sub = this.route.fragment.subscribe((hash: string) => {
    if (hash) {
      const cmp = document.getElementById(hash);
      if (cmp) {
        cmp.scrollIntoView();
      }
    } else {
      window.scrollTo(0, 0);
    }
  });
}
0
Jorg Janke

@Fernando Echeverria Großartig! Dieser Code funktioniert jedoch nicht im Hash-Router oder Lazy-Router. weil sie keine Standortänderungen auslösen. kann dies versuchen:

private lastRouteUrl: string[] = []
  

ngOnInit(): void {
  this.router.events.subscribe((ev) => {
    const len = this.lastRouteUrl.length
    if (ev instanceof NavigationEnd) {
      this.lastRouteUrl.Push(ev.url)
      if (len > 1 && ev.url === this.lastRouteUrl[len - 2]) {
        return
      }
      window.scrollTo(0, 0)
    }
  })
}

0
Witt Bulter