Skip to content

Commit 66ddadc

Browse files
committed
Fix infinite route-loader spinner from per-navigation root cache invalidation
BrowserInitService invalidated the root /server/api endpoint cache on every NavigationStart. That marks the root request stale (RootDataService .invalidateRootCache -> RequestService.setStaleByHref). HALEndpointService .getEndpointMapAt, used by getEndpoint() for every data request, discards a stale root via filter(rd => !rd.isStale), and its re-fetch can lose the race with the next invalidation, so getEndpoint() never resolves, the route resolver never completes, and the route loader spins forever. The root endpoint map is static between navigations, so invalidating it on every navigation is unnecessary. Remove the per-NavigationStart invalidation and keep the one-time invalidation at app init; backend availability is still established there and surfaces through normal request failures. Fixes #3584, #3697.
1 parent f112533 commit 66ddadc

2 files changed

Lines changed: 54 additions & 16 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* The contents of this file are subject to the license and copyright
3+
* detailed in the LICENSE and NOTICE files at the root of the source
4+
* tree and available online at
5+
*
6+
* http://www.dspace.org/license/
7+
*/
8+
import { NavigationStart } from '@angular/router';
9+
import { Subject } from 'rxjs';
10+
11+
import { BrowserInitService } from './browser-init.service';
12+
13+
describe('BrowserInitService', () => {
14+
describe('listenForRouteChanges', () => {
15+
let service: BrowserInitService;
16+
let rootDataServiceSpy;
17+
let routerEvents$: Subject<any>;
18+
19+
beforeEach(() => {
20+
rootDataServiceSpy = jasmine.createSpyObj('rootDataService', ['invalidateRootCache']);
21+
routerEvents$ = new Subject();
22+
23+
service = new BrowserInitService(
24+
null, null, null, null, null, null, null, null, null, null, null, null, null, null,
25+
rootDataServiceSpy,
26+
{ events: routerEvents$.asObservable() } as any,
27+
null, null, null, null,
28+
);
29+
30+
(service as any).listenForRouteChanges();
31+
});
32+
33+
it('should invalidate the root endpoint cache once, at init', () => {
34+
expect(rootDataServiceSpy.invalidateRootCache).toHaveBeenCalledTimes(1);
35+
});
36+
37+
it('should not invalidate the root endpoint cache again on subsequent NavigationStart events', () => {
38+
routerEvents$.next(new NavigationStart(1, '/some-route'));
39+
routerEvents$.next(new NavigationStart(2, '/another-route'));
40+
41+
expect(rootDataServiceSpy.invalidateRootCache).toHaveBeenCalledTimes(1);
42+
});
43+
});
44+
});

src/modules/app/browser-init.service.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ import {
1010
Injectable,
1111
TransferState,
1212
} from '@angular/core';
13-
import {
14-
NavigationStart,
15-
Router,
16-
} from '@angular/router';
13+
import { Router } from '@angular/router';
1714
import {
1815
APP_CONFIG,
1916
APP_CONFIG_STATE,
@@ -226,21 +223,18 @@ export class BrowserInitService extends InitService {
226223
}
227224

228225
/**
229-
* Listen to all router events. Every time a new navigation starts, invalidate the cache
230-
* for the root endpoint. That way we retrieve it once per routing operation to ensure the
231-
* backend is not down. But if the guard is called multiple times during the same routing
232-
* operation, the cached version is used.
226+
* Invalidate the cache for the root endpoint once, at startup, so the first request for it
227+
* is guaranteed to hit the backend rather than serve a value cached before the app booted.
228+
*
229+
* This used to also run on every `NavigationStart`, but the root endpoint map does not change
230+
* between navigations, so that repeated invalidation was unnecessary. Worse, it could mark the
231+
* root request stale while {@link HALEndpointService} was still resolving it for an in-flight
232+
* data request; the stale value is filtered out of the endpoint map and the retry for it can
233+
* lose the race with the next invalidation, so `getEndpoint()` never resolves and the route
234+
* that depends on it never finishes loading.
233235
*/
234236
protected listenForRouteChanges(): void {
235-
// we'll always be too late for the first NavigationStart event with the router subscribe below,
236-
// so this statement is for the very first route operation.
237237
this.rootDataService.invalidateRootCache();
238-
239-
this.router.events.pipe(
240-
filter(event => event instanceof NavigationStart),
241-
).subscribe(() => {
242-
this.rootDataService.invalidateRootCache();
243-
});
244238
}
245239

246240
}

0 commit comments

Comments
 (0)