typescript
62 lines · 10 steps
A scroll-preserving RouteReuseStrategy in Angular
A custom RouteReuseStrategy that detaches marked routes and restores their scroll position when you navigate back.
Explained by
highlit
1import { Injectable } from '@angular/core';
2import {
3 ActivatedRouteSnapshot,
4 DetachedRouteHandle,
5 RouteReuseStrategy,
6} from '@angular/router';
7
8interface StoredRoute {
9 handle: DetachedRouteHandle;
10 scrollTop: number;
11 scrollLeft: number;
12}
13
14@Injectable()
15export class ScrollPreservingReuseStrategy implements RouteReuseStrategy {
16 private readonly store = new Map<string, StoredRoute>();
17
18 shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
19 return future.routeConfig === curr.routeConfig;
20 }
21
22 shouldDetach(route: ActivatedRouteSnapshot): boolean {
23 return route.routeConfig?.data?.['reuse'] === true;
24 }
25
26 store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void {
27 const key = this.keyFor(route);
28 if (!handle) {
29 this.store.delete(key);
30 return;
31 }
32 const el = document.scrollingElement ?? document.documentElement;
33 this.store.set(key, {
34 handle,
35 scrollTop: el.scrollTop,
36 scrollLeft: el.scrollLeft,
37 });
38 }
39
40 shouldAttach(route: ActivatedRouteSnapshot): boolean {
41 return this.store.has(this.keyFor(route));
42 }
43
44 retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
45 const stored = this.store.get(this.keyFor(route));
46 if (!stored) {
47 return null;
48 }
49 requestAnimationFrame(() => {
50 const el = document.scrollingElement ?? document.documentElement;
51 el.scrollTo({ top: stored.scrollTop, left: stored.scrollLeft });
52 });
53 return stored.handle;
54 }
55
56 private keyFor(route: ActivatedRouteSnapshot): string {
57 return route.pathFromRoot
58 .map((r) => r.url.map((seg) => seg.path).join('/'))
59 .filter(Boolean)
60 .join('/') || '/';
61 }
62}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Implementing RouteReuseStrategy lets you cache and re-attach whole component subtrees instead of rebuilding them.
- 2Route data flags are a clean way to opt specific routes into reuse without hardcoding paths.
- 3Deferring scroll restoration to requestAnimationFrame ensures the reattached view is laid out before you scroll it.
Related explainers
typescript
import { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; import localeFrExtra from '@angular/common/locales/extra/fr'; import localeDe from '@angular/common/locales/de';
Locale-aware bootstrapping in Angular
i18n
localization
dependency-injection
Intermediate
8 steps
typescript
import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import * as Joi from 'joi';
Validating env config at boot in NestJS
configuration
schema-validation
environment-variables
Intermediate
8 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; import { InjectRepository } from '@nestjs/typeorm';
A cache-aside country lookup in NestJS
cache-aside
dependency-injection
batch-lookup
Intermediate
8 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core'; interface Preferences { theme: 'light' | 'dark';
A signal-based preferences store in Angular
signals
state-management
persistence
Intermediate
7 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
php
<?php namespace App\Services;
Building a cached daily leaderboard in Laravel
caching
aggregation
eager-loading
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-scroll-preserving-routereusestrategy-in-angular-explained-typescript-420b/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.