Прокрутка страницы вверх при смене маршрута в Angular 2+
Решение: глобальный сервис с подпиской на события роутера
Лучший способ — использовать встроенный сервис Router и подписаться на события навигации. Angular предоставляет NavigationEnd — событие, которое срабатывает после успешного завершения перехода. В этом обработчике мы вызываем window.scrollTo(0, 0).
Чтобы решение работало для всего приложения, создадим отдельный сервис или просто добавим логику в корневой компонент AppComponent.
Вариант 1: Логика в AppComponent (простой)
Подходит для небольших приложений. Подписываемся на события роутера в конструкторе:
// app.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
private subscription: Subscription;
constructor(private router: Router) {}
ngOnInit() {
this.subscription = this.router.events
.pipe(filter(event => event instanceof NavigationEnd))
.subscribe(() => {
window.scrollTo(0, 0);
});
}
ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}
Вариант 2: Вынос в сервис (рекомендуемый)
Лучше инкапсулировать логику в отдельный сервис, который можно переиспользовать и тестировать:
// scroll-to-top.service.ts
import { Injectable, OnDestroy } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
import { Subscription } from 'rxjs';
@Injectable({
providedIn: 'root' // сервис будет синглтоном на всё приложение
})
export class ScrollToTopService implements OnDestroy {
private subscription: Subscription;
constructor(private router: Router) {
this.subscription = this.router.events
.pipe(filter(event => event instanceof NavigationEnd))
.subscribe(() => {
window.scrollTo({ top: 0, behavior: 'auto' }); // 'smooth' для плавной прокрутки
});
}
ngOnDestroy() {
this.subscription?.unsubscribe();
}
}
Затем просто внедряем сервис в AppComponent:
// app.component.ts
import { Component } from '@angular/core';
import { ScrollToTopService } from './scroll-to-top.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
constructor(private scrollToTopService: ScrollToTopService) {
// Сервис сам подпишется в конструкторе
}
}
Решение проблемы с историей браузера (scroll restoration)
По умолчанию браузер восстанавливает позицию скролла при навигации «Назад»/«Вперед». Angular это мешает, так как наш код сбрасывает скролл в 0 после каждого перехода. Чтобы это исправить, нужно использовать Scroll и ViewportScroller из @angular/common.
Angular предоставляет встроенный механизм для управления скроллом через RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled' }). Это автоматически восстанавливает позицию скролла при переходе по истории и сбрасывает в 0 при обычной навигации.
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'products', component: ProductsComponent },
{ path: 'about', component: AboutComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled' // включает восстановление скролла
})],
exports: [RouterModule]
})
export class AppRoutingModule { }
Этот подход решает обе задачи:
- Прокрутка вверх при обычном переходе по ссылке или программной навигации.
- Восстановление позиции при использовании кнопок «Назад»/«Вперед».
Важно: Не нужно комбинировать scrollPositionRestoration с ручной подпиской на NavigationEnd, иначе будет конфликт. Используйте только один из методов.
Альтернативный подход: использование директивы
Если вы не хотите менять конфигурацию роутера или вам нужно больше контроля (например, плавная прокрутка с задержкой), можно создать директиву, которая будет применяться к элементу <router-outlet>:
// scroll-to-top.directive.ts
import { Directive, HostListener } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
@Directive({
selector: '[appScrollToTop]'
})
export class ScrollToTopDirective {
constructor(private router: Router) {
this.router.events
.pipe(filter(event => event instanceof NavigationEnd))
.subscribe(() => {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
}
}
Применение в шаблоне:
<router-outlet appScrollToTop></router-outlet>
Плюсы:
- Не нужно менять
AppComponentили сервис. - Легко добавить/убрать для отдельных частей приложения.
- Можно передавать параметры (например,
@Input() offset: number = 0).
Минусы:
- Не решает проблему с историей браузера — для этого всё равно нужен
scrollPositionRestorationв конфигурации роутера. - Если у вас несколько
<router-outlet>, придётся вешать директиву на каждый.
Этот вариант подходит, когда нужно быстро добавить функциональность без рефакторинга существующего кода.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги
CI/CD для Kotlin проектов с GitHub Actions: пошаговое руководство