fix(router): NavigatonError and NavigationCancel should be emitted after resetting the URL (#20803)

PR Close #20803
This commit is contained in:
vsavkin 2017-12-05 11:34:34 -05:00 committed by Jason Aden
parent d41d2c460a
commit d8cc09b76c
2 changed files with 106 additions and 6 deletions

View File

@ -745,12 +745,14 @@ export class Router {
},
(e: any) => {
if (isNavigationCancelingError(e)) {
this.resetUrlToCurrentUrlTree();
this.navigated = true;
this.resetStateAndUrl(storedState, storedUrl, rawUrl);
(this.events as Subject<Event>)
.next(new NavigationCancel(id, this.serializeUrl(url), e.message));
resolvePromise(false);
} else {
this.resetStateAndUrl(storedState, storedUrl, rawUrl);
(this.events as Subject<Event>)
.next(new NavigationError(id, this.serializeUrl(url), e));
try {
@ -759,15 +761,17 @@ export class Router {
rejectPromise(ee);
}
}
(this as{routerState: RouterState}).routerState = storedState;
this.currentUrlTree = storedUrl;
this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, rawUrl);
this.resetUrlToCurrentUrlTree();
});
});
}
private resetStateAndUrl(storedState: RouterState, storedUrl: UrlTree, rawUrl: UrlTree): void {
(this as{routerState: RouterState}).routerState = storedState;
this.currentUrlTree = storedUrl;
this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, rawUrl);
this.resetUrlToCurrentUrlTree();
}
private resetUrlToCurrentUrlTree(): void {
this.location.replaceState(this.urlSerializer.serialize(this.rawUrlTree));
}

View File

@ -17,6 +17,7 @@ import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
import {of } from 'rxjs/observable/of';
import {map} from 'rxjs/operator/map';
import {log} from 'util';
import {forEach} from '../src/utils/collection';
import {RouterTestingModule, SpyNgModuleFactoryLoader} from '../testing';
@ -840,6 +841,92 @@ describe('Integration', () => {
]);
})));
it('should dispatch NavigationError after the url has been reset back', fakeAsync(() => {
const router = TestBed.get(Router);
const location = TestBed.get(Location);
const fixture = createRoot(router, RootCmp);
router.resetConfig(
[{path: 'simple', component: SimpleCmp}, {path: 'throwing', component: ThrowingCmp}]);
router.navigateByUrl('/simple');
advance(fixture);
let routerUrlBeforeEmittingError;
let locationUrlBeforeEmittingError;
router.events.forEach((e: any) => {
if (e instanceof NavigationError) {
routerUrlBeforeEmittingError = router.url;
locationUrlBeforeEmittingError = location.path();
}
});
router.navigateByUrl('/throwing').catch(() => null);
advance(fixture);
expect(routerUrlBeforeEmittingError).toEqual('/simple');
expect(locationUrlBeforeEmittingError).toEqual('/simple');
}));
it('should not trigger another navigation when resetting the url back due to a NavigationError',
fakeAsync(() => {
const router = TestBed.get(Router);
router.onSameUrlNavigation = 'reload';
const fixture = createRoot(router, RootCmp);
router.resetConfig(
[{path: 'simple', component: SimpleCmp}, {path: 'throwing', component: ThrowingCmp}]);
const events: any[] = [];
router.events.forEach((e: any) => {
if (e instanceof NavigationStart) {
events.push(e.url);
}
});
router.navigateByUrl('/simple');
advance(fixture);
router.navigateByUrl('/throwing').catch(() => null);
advance(fixture);
// we do not trigger another navigation to /simple
expect(events).toEqual(['/simple', '/throwing']);
}));
it('should dispatch NavigationCancel after the url has been reset back', fakeAsync(() => {
TestBed.configureTestingModule(
{providers: [{provide: 'returnsFalse', useValue: () => false}]});
const router = TestBed.get(Router);
const location = TestBed.get(Location);
const fixture = createRoot(router, RootCmp);
router.resetConfig([
{path: 'simple', component: SimpleCmp},
{path: 'throwing', loadChildren: 'doesnotmatter', canLoad: ['returnsFalse']}
]);
router.navigateByUrl('/simple');
advance(fixture);
let routerUrlBeforeEmittingError;
let locationUrlBeforeEmittingError;
router.events.forEach((e: any) => {
if (e instanceof NavigationCancel) {
routerUrlBeforeEmittingError = router.url;
locationUrlBeforeEmittingError = location.path();
}
});
(<any>location).simulateHashChange('/throwing');
advance(fixture);
expect(locationUrlBeforeEmittingError).toEqual('/simple');
}));
it('should support custom error handlers', fakeAsync(inject([Router], (router: Router) => {
router.errorHandler = (error) => 'resolvedValue';
const fixture = createRoot(router, RootCmp);
@ -3915,6 +4002,12 @@ class RootCmpWithOnInit {
class RootCmpWithTwoOutlets {
}
@Component({selector: 'throwing-cmp', template: ''})
class ThrowingCmp {
constructor() { throw new Error('Throwing Cmp'); }
}
function advance(fixture: ComponentFixture<any>): void {
tick();
@ -3955,6 +4048,7 @@ function createRoot(router: Router, type: any): ComponentFixture<any> {
RelativeLinkInIfCmp,
RootCmpWithTwoOutlets,
EmptyQueryParamsCmp,
ThrowingCmp
],
@ -3982,6 +4076,7 @@ function createRoot(router: Router, type: any): ComponentFixture<any> {
RelativeLinkInIfCmp,
RootCmpWithTwoOutlets,
EmptyQueryParamsCmp,
ThrowingCmp
],
@ -4010,6 +4105,7 @@ function createRoot(router: Router, type: any): ComponentFixture<any> {
RelativeLinkInIfCmp,
RootCmpWithTwoOutlets,
EmptyQueryParamsCmp,
ThrowingCmp
]
})
class TestModule {