Skip to content

Commit 25e5b2f

Browse files
vsavkinalxhub
authored andcommitted
fix(router): make setUpLocationChangeListener idempotent
1 parent 307c469 commit 25e5b2f

File tree

2 files changed

+47
-17
lines changed

2 files changed

+47
-17
lines changed

modules/@angular/router/src/router.ts

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -382,23 +382,25 @@ export class Router {
382382
setUpLocationChangeListener(): void {
383383
// Zone.current.wrap is needed because of the issue with RxJS scheduler,
384384
// which does not work properly with zone.js in IE and Safari
385-
this.locationSubscription = <any>this.location.subscribe(Zone.current.wrap((change: any) => {
386-
const rawUrlTree = this.urlSerializer.parse(change['url']);
387-
const lastNavigation = this.navigations.value;
388-
389-
// If the user triggers a navigation imperatively (e.g., by using navigateByUrl),
390-
// and that navigation results in 'replaceState' that leads to the same URL,
391-
// we should skip those.
392-
if (lastNavigation && lastNavigation.imperative &&
393-
lastNavigation.rawUrl.toString() === rawUrlTree.toString()) {
394-
return;
395-
}
385+
if (!this.locationSubscription) {
386+
this.locationSubscription = <any>this.location.subscribe(Zone.current.wrap((change: any) => {
387+
const rawUrlTree = this.urlSerializer.parse(change['url']);
388+
const lastNavigation = this.navigations.value;
389+
390+
// If the user triggers a navigation imperatively (e.g., by using navigateByUrl),
391+
// and that navigation results in 'replaceState' that leads to the same URL,
392+
// we should skip those.
393+
if (lastNavigation && lastNavigation.imperative &&
394+
lastNavigation.rawUrl.toString() === rawUrlTree.toString()) {
395+
return;
396+
}
396397

397-
setTimeout(() => {
398-
this.scheduleNavigation(
399-
rawUrlTree, false, {skipLocationChange: change['pop'], replaceUrl: true});
400-
}, 0);
401-
}));
398+
setTimeout(() => {
399+
this.scheduleNavigation(
400+
rawUrlTree, false, {skipLocationChange: change['pop'], replaceUrl: true});
401+
}, 0);
402+
}));
403+
}
402404
}
403405

404406
/**
@@ -443,7 +445,12 @@ export class Router {
443445
/**
444446
* Disposes of the router.
445447
*/
446-
dispose(): void { this.locationSubscription.unsubscribe(); }
448+
dispose(): void {
449+
if (this.locationSubscription) {
450+
this.locationSubscription.unsubscribe();
451+
this.locationSubscription = null;
452+
}
453+
}
447454

448455
/**
449456
* Applies an array of commands to the current url tree and creates a new url tree.

modules/@angular/router/test/router.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {Location} from '@angular/common';
910
import {TestBed} from '@angular/core/testing';
1011

1112
import {ResolveData} from '../src/config';
@@ -32,6 +33,28 @@ describe('Router', () => {
3233
});
3334
});
3435

36+
describe('setUpLocationChangeListener', () => {
37+
beforeEach(() => { TestBed.configureTestingModule({imports: [RouterTestingModule]}); });
38+
39+
it('should be indempotent', () => {
40+
const r: Router = TestBed.get(Router);
41+
const location: Location = TestBed.get(Location);
42+
43+
r.setUpLocationChangeListener();
44+
const a = (<any>r).locationSubscription;
45+
r.setUpLocationChangeListener();
46+
const b = (<any>r).locationSubscription;
47+
48+
expect(a).toBe(b);
49+
50+
r.dispose();
51+
r.setUpLocationChangeListener();
52+
const c = (<any>r).locationSubscription;
53+
54+
expect(c).not.toBe(b);
55+
});
56+
});
57+
3558
describe('PreActivation', () => {
3659
const serializer = new DefaultUrlSerializer();
3760
const inj = {get: (token: any) => () => `${token}_value`};

0 commit comments

Comments
 (0)