Skip to content

Instantly share code, notes, and snippets.

@jsonberry jsonberry/app.component.ts
Last active Feb 20, 2019

Embed
What would you like to do?
Reactive Angular Scroll Position Restoration with RxJS
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { Event, NavigationEnd, NavigationStart, Router } from '@angular/router';
import { asyncScheduler } from 'rxjs';
import { filter, observeOn, scan } from 'rxjs/operators';
interface ScrollPositionRestore {
event: Event;
positions: { [K: number]: number };
trigger: 'imperative' | 'popstate';
idToRestore: number;
}
@Component({
selector: 'app-root',
template: `
<app-header></app-header>
<div>
<app-side-nav></app-side-nav>
<main #contentArea>
<router-outlet></router-outlet>
</main>
</div>
`
})
export class AppComponent implements OnInit {
@ViewChild('contentArea') private contentArea: ElementRef<HTMLMainElement>;
constructor(private router: Router) {}
ngOnInit() {
this.router.events
.pipe(
filter(
event =>
event instanceof NavigationStart || event instanceof NavigationEnd,
),
scan<Event, ScrollPositionRestore>((acc, event) => ({
event,
positions: {
...acc.positions,
...(event instanceof NavigationStart
? {
[event.id]: this.contentArea.nativeElement.scrollTop,
}
: {}),
},
trigger:
event instanceof NavigationStart
? event.navigationTrigger
: acc.trigger,
idToRestore:
(event instanceof NavigationStart &&
event.restoredState &&
event.restoredState.navigationId + 1) ||
acc.idToRestore,
})),
filter(
({ event, trigger }) => event instanceof NavigationEnd && !!trigger,
),
observeOn(asyncScheduler),
)
.subscribe(({ trigger, positions, idToRestore }) => {
if (trigger === 'imperative') {
this.contentArea.nativeElement.scrollTop = 0;
}
if (trigger === 'popstate') {
this.contentArea.nativeElement.scrollTop = positions[idToRestore];
}
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.