Skip to content

Commit a3bdc1d

Browse files
committed
feat: add WIP RouterHistoryStore#nextUrl$ property
1 parent b284f43 commit a3bdc1d

File tree

3 files changed

+246
-3
lines changed

3 files changed

+246
-3
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {
2+
Event as NgRouterEvent,
3+
NavigationEnd,
4+
NavigationStart,
5+
} from '@angular/router';
6+
7+
export const routerEvents: readonly NgRouterEvent[] = [
8+
// 1. Navigate to the root path ‘/’, which redirects me to the homepage
9+
// Current: Home
10+
// Previous: None
11+
// Next: None
12+
new NavigationStart(1, '/', 'imperative', null),
13+
new NavigationEnd(1, '/', '/home'),
14+
15+
// 2. Click a menu link to navigate to the About page
16+
// Current: About
17+
// Previous: Home
18+
// Next: None
19+
new NavigationStart(2, '/about', 'imperative', null),
20+
new NavigationEnd(2, '/about', '/about'),
21+
22+
// 3. Click a menu link to navigate to the Company page
23+
// Current: Company
24+
// Previous About
25+
// Next: None
26+
new NavigationStart(3, '/company', 'imperative', null),
27+
new NavigationEnd(3, '/company', '/company'),
28+
29+
// 4. Click the back button
30+
// Current: About
31+
// Previous: Home
32+
// Next: Company
33+
new NavigationStart(4, '/about', 'popstate', { navigationId: 2 }),
34+
new NavigationEnd(4, '/about', '/about'),
35+
36+
// 5. Click a menu link to navigate to the Products page
37+
// Current: Products
38+
// Previous: About
39+
// Next: None
40+
new NavigationStart(5, '/products', 'imperative', null),
41+
new NavigationEnd(5, '/products', '/products'),
42+
43+
// 6. Click a menu link to navigate to the Home page
44+
// Current: Home
45+
// Previous: Products
46+
// Next: None
47+
new NavigationStart(6, '/home', 'imperative', null),
48+
new NavigationEnd(6, '/home', '/home'),
49+
50+
// 7. Click a menu link to navigate to the About page
51+
// Current: About
52+
// Previous: Home
53+
// Next: None
54+
new NavigationStart(7, '/about', 'imperative', null),
55+
new NavigationEnd(7, '/about', '/about'),
56+
57+
// 8. Click the back button
58+
// Current: Home
59+
// Previous: Products
60+
// Next: About
61+
new NavigationStart(8, '/home', 'popstate', { navigationId: 6 }),
62+
new NavigationEnd(8, '/home', '/home'),
63+
64+
// 9. Click the forward button
65+
// Current: About
66+
// Previous: Home
67+
// Next: None
68+
new NavigationStart(9, '/about', 'popstate', { navigationId: 7 }),
69+
new NavigationEnd(9, '/about', '/about'),
70+
71+
// 10. Click the back button
72+
// Current: Home
73+
// Previous: Products
74+
// Next: About
75+
new NavigationStart(10, '/home', 'popstate', { navigationId: 8 }),
76+
new NavigationEnd(10, '/home', '/home'),
77+
];

packages/router-component-store/src/lib/router-history-store/router-history.store.spec.ts

Lines changed: 137 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ function createTestComponent(name: string, selector: string) {
3030
>&lt; Back</a
3131
>
3232
33+
<a
34+
id="forward-link"
35+
*ngIf="routerHistory.nextUrl$ | async as nextUrl"
36+
[href]="nextUrl"
37+
(click)="onNext($event)"
38+
>&gt; Next</a
39+
>
40+
3341
<a id="home-link" routerLink="/">Home</a>
3442
<a id="about-link" routerLink="about">About</a>
3543
<a id="company-link" routerLink="company">Company</a>
@@ -45,6 +53,11 @@ class TestAppComponent {
4553
event.preventDefault();
4654
this.routerHistory.onNavigateBack();
4755
}
56+
57+
onNext(event: MouseEvent) {
58+
event.preventDefault();
59+
this.routerHistory.onNavigateForward();
60+
}
4861
}
4962

5063
describe(RouterHistoryStore.name, () => {
@@ -98,55 +111,176 @@ describe(RouterHistoryStore.name, () => {
98111
}
99112

100113
it('the URLs behave like the History API when navigating using links', async () => {
101-
expect.assertions(2);
114+
expect.assertions(3);
102115

103116
const { click, routerHistory } = await setup();
104117

105118
// At Home
119+
// Previous: None
120+
// Next: None
106121
await click('#about-link');
107122
// At About
123+
// Previous: Home
124+
// Next: None
108125
await click('#company-link');
109126
// At Company
127+
// Previous: About
128+
// Next: None
110129
await click('#products-link');
111130
// At Products
131+
// Previous: Company
132+
// Next: None
112133

113134
expect(await firstValueFrom(routerHistory.currentUrl$)).toBe('/products');
114135
expect(await firstValueFrom(routerHistory.previousUrl$)).toBe('/company');
136+
expect(await firstValueFrom(routerHistory.nextUrl$)).toBe(undefined);
115137
});
116138

117139
it('the URLs behave like the History API when navigating back', async () => {
118-
expect.assertions(2);
140+
expect.assertions(3);
141+
142+
const { click, routerHistory } = await setup();
143+
144+
// At Home
145+
// Previous: None
146+
// Next: None
147+
await click('#about-link');
148+
// At About
149+
// Previous: Home
150+
// Next: None
151+
await click('#company-link');
152+
// At Company
153+
// Previous: About
154+
// Next: None
155+
await click('#back-link');
156+
// At About
157+
// Previous: Home
158+
// Next: Company
159+
160+
expect(await firstValueFrom(routerHistory.currentUrl$)).toBe('/about');
161+
expect(await firstValueFrom(routerHistory.previousUrl$)).toBe('/home');
162+
expect(await firstValueFrom(routerHistory.nextUrl$)).toBe('/company');
163+
});
164+
165+
it('the URLs behave like the History API when navigating back twice', async () => {
166+
expect.assertions(3);
167+
168+
const { click, routerHistory } = await setup();
169+
170+
// At Home
171+
// Previous: None
172+
// Next: None
173+
await click('#about-link');
174+
// At About
175+
// Previous: Home
176+
// Next: None
177+
await click('#company-link');
178+
// At Company
179+
// Previous: About
180+
// Next: None
181+
await click('#back-link');
182+
// At About
183+
// Previous: Home
184+
// Next: Company
185+
await click('#back-link');
186+
// At Home
187+
// Previous: None
188+
// Next: About
189+
190+
expect(await firstValueFrom(routerHistory.currentUrl$)).toBe('/home');
191+
expect(await firstValueFrom(routerHistory.previousUrl$)).toBe(undefined);
192+
expect(await firstValueFrom(routerHistory.nextUrl$)).toBe('/about');
193+
});
194+
195+
it('the URLs behave like the History API when navigating back twice then forward', async () => {
196+
expect.assertions(3);
119197

120198
const { click, routerHistory } = await setup();
121199

122200
// At Home
201+
// Previous: None
202+
// Next: None
123203
await click('#about-link');
124204
// At About
205+
// Previous: Home
206+
// Next: None
125207
await click('#company-link');
126208
// At Company
209+
// Previous: About
210+
// Next: None
127211
await click('#back-link');
128212
// At About
213+
// Previous: Home
214+
// Next: Company
215+
await click('#back-link');
216+
// At Home
217+
// Previous: None
218+
// Next: About
219+
await click('#forward-link');
220+
// At About
221+
// Previous: Home
222+
// Next: Company
129223

130224
expect(await firstValueFrom(routerHistory.currentUrl$)).toBe('/about');
131225
expect(await firstValueFrom(routerHistory.previousUrl$)).toBe('/home');
226+
expect(await firstValueFrom(routerHistory.nextUrl$)).toBe('/company');
132227
});
133228

134229
it('the URLs behave like the History API when navigating back then using links', async () => {
135-
expect.assertions(2);
230+
expect.assertions(3);
136231

137232
const { click, routerHistory } = await setup();
138233

139234
// At Home
235+
// Previous: None
236+
// Next: None
140237
await click('#about-link');
141238
// At About
239+
// Previous: Home
240+
// Next: None
142241
await click('#company-link');
143242
// At Company
243+
// Previous: About
244+
// Next: None
144245
await click('#back-link');
145246
// At About
247+
// Previous: Home
248+
// Next: Company
146249
await click('#products-link');
147250
// At Products
251+
// Previous: About
252+
// Next: None
148253

149254
expect(await firstValueFrom(routerHistory.currentUrl$)).toBe('/products');
150255
expect(await firstValueFrom(routerHistory.previousUrl$)).toBe('/about');
256+
expect(await firstValueFrom(routerHistory.nextUrl$)).toBe(undefined);
257+
});
258+
259+
it('the URLs behave like the History API when navigating back then forward', async () => {
260+
expect.assertions(3);
261+
262+
const { click, routerHistory } = await setup();
263+
264+
// At Home
265+
await click('#about-link');
266+
// At About
267+
// Previous: Home
268+
// Next: None
269+
await click('#company-link');
270+
// At Company
271+
// Previous: About
272+
// Next: None
273+
await click('#back-link');
274+
// At About
275+
// Previous: Home
276+
// Next: Company
277+
await click('#forward-link');
278+
// At Company
279+
// Previous: About
280+
// Next: None
281+
282+
expect(await firstValueFrom(routerHistory.currentUrl$)).toBe('/company');
283+
expect(await firstValueFrom(routerHistory.previousUrl$)).toBe('/about');
284+
expect(await firstValueFrom(routerHistory.nextUrl$)).toBe(undefined);
151285
});
152286
});

packages/router-component-store/src/lib/router-history-store/router-history.store.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,38 @@ export class RouterHistoryStore extends ComponentStore<RouterHistoryState> {
127127
this.#latestRouterNavigatedSequence$,
128128
([, navigationEnd]) => navigationEnd.urlAfterRedirects
129129
);
130+
/**
131+
* The next URL when taking `popstate` events into account.
132+
*
133+
* `undefined` is emitted when the current navigation is the last in the
134+
* navigation history.
135+
*/
136+
nextUrl$: Observable<string | undefined> = this.select(
137+
this.#history$,
138+
this.#maxNavigatedId$,
139+
(history, maxNavigatedId) => {
140+
if (maxNavigatedId === 1) {
141+
return undefined;
142+
}
143+
144+
const [sourceNavigationStart] = this.#findSourceNavigatedSequence(
145+
maxNavigatedId,
146+
history
147+
);
148+
149+
if (sourceNavigationStart.id === maxNavigatedId) {
150+
return undefined;
151+
}
152+
153+
const nextNavigationId = sourceNavigationStart.id + 1;
154+
const [, nextNavigationEnd] = this.#findSourceNavigatedSequence(
155+
nextNavigationId,
156+
history
157+
);
158+
159+
return nextNavigationEnd.urlAfterRedirects;
160+
}
161+
);
130162
/**
131163
* The previous URL when taking `popstate` events into account.
132164
*

0 commit comments

Comments
 (0)