refactor(router): make sure redirect within NavigationStart event works (#25740)

PR Close #25740
This commit is contained in:
Jason Aden 2018-09-11 19:12:57 -07:00 committed by Alex Rickabaugh
parent b7baf632c0
commit c091d40fb0
2 changed files with 52 additions and 14 deletions

View File

@ -370,11 +370,15 @@ export class Router {
this.processNavigations(); this.processNavigations();
} }
private setupNavigations(transitions: Observable<NavigationTransition>): Observable<NavigationTransition> { private setupNavigations(transitions: Observable<NavigationTransition>):
Observable<NavigationTransition> {
return transitions.pipe( return transitions.pipe(
filter(t => t.id !== 0), filter(t => t.id !== 0), mergeMap(t => Promise.resolve(t)),
// Extract URL // Extract URL
map(t => ({...t, extractedUrl: this.urlHandlingStrategy.extract(t.rawUrl)}) as NavigationTransition), map(t => ({
...t, //
extractedUrl: this.urlHandlingStrategy.extract(t.rawUrl), //
} as NavigationTransition)),
// Using switchMap so we cancel executing navigations when a new one comes in // Using switchMap so we cancel executing navigations when a new one comes in
switchMap(t => { switchMap(t => {
@ -391,10 +395,16 @@ export class Router {
tap(t => this.urlUpdateStrategy === 'eager' && !t.extras.skipLocationChange && tap(t => this.urlUpdateStrategy === 'eager' && !t.extras.skipLocationChange &&
this.setBrowserUrl(t.rawUrl, !!t.extras.replaceUrl, t.id)), this.setBrowserUrl(t.rawUrl, !!t.extras.replaceUrl, t.id)),
// Fire NavigationStart event // Fire NavigationStart event
tap(t => mergeMap(t => {
const transition = this.transitions.getValue();
(this.events as Subject<Event>) (this.events as Subject<Event>)
.next(new NavigationStart( .next(new NavigationStart(
t.id, this.serializeUrl(t.extractedUrl), t.source, t.state))), t.id, this.serializeUrl(t.extractedUrl), t.source, t.state));
if (transition !== this.transitions.getValue()) {
EMPTY;
}
return [t];
}),
// This delay is required to match old behavior that forced navigation to // This delay is required to match old behavior that forced navigation to
// always be async // always be async
@ -450,8 +460,7 @@ export class Router {
preActivation.initialize(this.rootContexts); preActivation.initialize(this.rootContexts);
return {...t, preActivation}; return {...t, preActivation};
}), }),
checkGuards(), checkGuards(), tap(t => this.triggerEvent(new GuardsCheckEnd(
tap(t => this.triggerEvent(new GuardsCheckEnd(
t.id, this.serializeUrl(t.extractedUrl), t.id, this.serializeUrl(t.extractedUrl),
this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !, this.serializeUrl(t.urlAfterRedirects), t.targetSnapshot !,
!!t.guardsResult))), !!t.guardsResult))),

View File

@ -14,7 +14,7 @@ import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ActivatedRoute, ActivatedRouteSnapshot, ActivationEnd, ActivationStart, CanActivate, CanDeactivate, ChildActivationEnd, ChildActivationStart, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, Router, RouterEvent, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlSerializer, UrlTree} from '@angular/router'; import {ActivatedRoute, ActivatedRouteSnapshot, ActivationEnd, ActivationStart, CanActivate, CanDeactivate, ChildActivationEnd, ChildActivationStart, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, Router, RouterEvent, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlSerializer, UrlTree} from '@angular/router';
import {Observable, Observer, Subscription, of } from 'rxjs'; import {Observable, Observer, Subscription, of } from 'rxjs';
import {filter, map} from 'rxjs/operators'; import {filter, first, map, tap} from 'rxjs/operators';
import {forEach} from '../src/utils/collection'; import {forEach} from '../src/utils/collection';
import {RouterTestingModule, SpyNgModuleFactoryLoader} from '../testing'; import {RouterTestingModule, SpyNgModuleFactoryLoader} from '../testing';
@ -2970,6 +2970,35 @@ describe('Integration', () => {
[NavigationEnd, '/user/fedor'] [NavigationEnd, '/user/fedor']
]); ]);
}))); })));
it('should allow redirection in NavigationStart', fakeAsync(inject([Router], (router: Router) => {
const fixture = createRoot(router, RootCmp);
router.resetConfig([
{path: 'blank', component: UserCmp},
{path: 'user/:name', component: BlankCmp},
]);
const navigateSpy = spyOn(router, 'navigate').and.callThrough();
const recordedEvents: any[] = [];
const navStart$ = router.events.pipe(
tap(e => recordedEvents.push(e)),
filter(e => e instanceof NavigationStart),
first()
);
navStart$.subscribe((e: NavigationStart | NavigationError) => {
router.navigate(['/blank'], {queryParams: {state: 'redirected'}, queryParamsHandling: 'merge'});
advance(fixture);
});
router.navigate(['/user/:fedor']);
advance(fixture);
expect(navigateSpy.calls.mostRecent().args[1].queryParams);
})));
}); });
describe('routerActiveLink', () => { describe('routerActiveLink', () => {