Skip to content

Commit

Permalink
fix(docs-infra): reference page restores scroll position or goes top …
Browse files Browse the repository at this point in the history
…when no anchor (#56478)

This contains follow-up fixes to 2a24397.
This commit updates scrolling on references page to scroll to the top
when there is no anchor in the URL. The behavior after the above commit would
be that the position doesn't change from whatever the previous page was
(potentially scrolled to the bottom). In addition, this restores the
previous scroll position when traversing through browser history rather
than always scrolling to the fragment.

PR Close #56478
  • Loading branch information
atscott authored and AndrewKushnir committed Jun 18, 2024
1 parent 67b2c33 commit f8654bd
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 11 deletions.
57 changes: 47 additions & 10 deletions adev/src/app/app-scroller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,40 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {ViewportScroller} from '@angular/common';
import {Injectable, inject, ApplicationRef} from '@angular/core';
import {
Injectable,
inject,
ApplicationRef,
afterNextRender,
EnvironmentInjector,
} from '@angular/core';
import {Scroll, Router} from '@angular/router';
import {filter, firstValueFrom, map, switchMap} from 'rxjs';
import {filter, firstValueFrom, map, switchMap, tap} from 'rxjs';

@Injectable({providedIn: 'root'})
export class AppScroller {
private readonly router = inject(Router);
private readonly viewportScroller = inject(ViewportScroller);
private readonly appRef = inject(ApplicationRef);
private readonly injector = inject(EnvironmentInjector);
disableScrolling = false;
private _lastScrollEvent?: Scroll;
private canScroll = false;
private cancelScroll?: () => void;
get lastScrollEvent(): Scroll | undefined {
return this._lastScrollEvent;
}

constructor() {
this.viewportScroller.setHistoryScrollRestoration('manual');
this.router.events
.pipe(
filter((e): e is Scroll => e instanceof Scroll),
tap((e) => {
this.cancelScroll?.();
this.canScroll = true;
this._lastScrollEvent = e;
}),
filter(() => !this.disableScrolling),
switchMap((e) => {
return firstValueFrom(
Expand All @@ -32,14 +50,33 @@ export class AppScroller {
);
}),
)
.subscribe(({anchor, position}) => {
if (position) {
this.viewportScroller.scrollToPosition(position);
} else if (anchor) {
this.viewportScroller.scrollToAnchor(anchor);
} else {
this.viewportScroller.scrollToPosition([0, 0]);
}
.subscribe(() => {
this.scroll();
});
}

scroll() {
if (!this._lastScrollEvent || !this.canScroll) {
return;
}
// Prevent double scrolling on the same event
this.canScroll = false;
const {anchor, position} = this._lastScrollEvent;

// Don't scroll during rendering
this.cancelScroll = afterNextRender(
{
write: () => {
if (position) {
this.viewportScroller.scrollToPosition(position);
} else if (anchor) {
this.viewportScroller.scrollToAnchor(anchor);
} else {
this.viewportScroller.scrollToPosition([0, 0]);
}
},
},
{injector: this.injector},
).destroy;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
} from '../constants/api-reference-prerender.constants';
import {WINDOW} from '@angular/docs';
import {Router, Scroll} from '@angular/router';
import {AppScroller} from '../../../app-scroller';

export const SCROLL_EVENT_DELAY = 20;
export const SCROLL_THRESHOLD = 20;
Expand All @@ -43,6 +44,7 @@ export class ReferenceScrollHandler implements OnDestroy, ReferenceScrollHandler
private readonly injector = inject(EnvironmentInjector);
private readonly window = inject(WINDOW);
private readonly router = inject(Router);
private readonly appScroller = inject(AppScroller);

private readonly cardOffsetTop = new Map<string, number>();
private resizeObserver: ResizeObserver | null = null;
Expand All @@ -65,7 +67,10 @@ export class ReferenceScrollHandler implements OnDestroy, ReferenceScrollHandler
this.router.routerState.root.fragment
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((fragment) => {
if (!fragment) {
// If there is no fragment or the scroll event has a position (traversing through history),
// allow the scroller to handler scrolling instead of going to the fragment
if (!fragment || this.appScroller.lastScrollEvent?.position) {
this.appScroller.scroll();
return;
}

Expand Down

0 comments on commit f8654bd

Please sign in to comment.