feat(router): add a Navigation type available during navigation (#27198)
Provides target URLs, Navigation, and `NavigationExtras` data. FW-613 PR Close #27198
This commit is contained in:
parent
73f6ed9be1
commit
d40af0c137
|
@ -87,14 +87,14 @@ import {UrlTree} from '../url_tree';
|
||||||
* </a>
|
* </a>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* And later the value can be read from the router through `router.getCurrentTransition.
|
* And later the value can be read from the router through `router.getCurrentNavigation.
|
||||||
* For example, to capture the `tracingId` above during the `NavigationStart` event:
|
* For example, to capture the `tracingId` above during the `NavigationStart` event:
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* // Get NavigationStart events
|
* // Get NavigationStart events
|
||||||
* router.events.pipe(filter(e => e instanceof NavigationStart)).subscribe(e => {
|
* router.events.pipe(filter(e => e instanceof NavigationStart)).subscribe(e => {
|
||||||
* const transition = router.getCurrentTransition();
|
* const navigation = router.getCurrentNavigation();
|
||||||
* tracingService.trace({id: transition.extras.state});
|
* tracingService.trace({id: navigation.extras.state.tracingId});
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
|
|
@ -70,8 +70,8 @@ export class NavigationStart extends RouterEvent {
|
||||||
navigationTrigger?: 'imperative'|'popstate'|'hashchange';
|
navigationTrigger?: 'imperative'|'popstate'|'hashchange';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This contains the navigation id that pushed the history record that the router navigates
|
* This reflects the state object that was previously supplied to the pushState call. This is
|
||||||
* back to. This is not null only when the navigation is triggered by a popstate event.
|
* not null only when the navigation is triggered by a popstate event.
|
||||||
*
|
*
|
||||||
* The router assigns a navigationId to every router transition/navigation. Even when the user
|
* The router assigns a navigationId to every router transition/navigation. Even when the user
|
||||||
* clicks on the back button in the browser, a new navigation id will be created. So from
|
* clicks on the back button in the browser, a new navigation id will be created. So from
|
||||||
|
@ -80,8 +80,10 @@ export class NavigationStart extends RouterEvent {
|
||||||
* states
|
* states
|
||||||
* and popstate events. In the latter case you can restore some remembered state (e.g., scroll
|
* and popstate events. In the latter case you can restore some remembered state (e.g., scroll
|
||||||
* position).
|
* position).
|
||||||
|
*
|
||||||
|
* See {@link NavigationExtras} for more information.
|
||||||
*/
|
*/
|
||||||
restoredState?: {navigationId: number}|null;
|
restoredState?: {[k: string]: any, navigationId: number}|null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
|
@ -91,7 +93,7 @@ export class NavigationStart extends RouterEvent {
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
navigationTrigger: 'imperative'|'popstate'|'hashchange' = 'imperative',
|
navigationTrigger: 'imperative'|'popstate'|'hashchange' = 'imperative',
|
||||||
/** @docsNotRequired */
|
/** @docsNotRequired */
|
||||||
restoredState: {navigationId: number}|null = null) {
|
restoredState: {[k: string]: any, navigationId: number}|null = null) {
|
||||||
super(id, url);
|
super(id, url);
|
||||||
this.navigationTrigger = navigationTrigger;
|
this.navigationTrigger = navigationTrigger;
|
||||||
this.restoredState = restoredState;
|
this.restoredState = restoredState;
|
||||||
|
|
|
@ -14,7 +14,7 @@ export {RouterOutlet} from './directives/router_outlet';
|
||||||
export {ActivationEnd, ActivationStart, ChildActivationEnd, ChildActivationStart, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouterEvent, RoutesRecognized, Scroll} from './events';
|
export {ActivationEnd, ActivationStart, ChildActivationEnd, ChildActivationStart, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouterEvent, RoutesRecognized, Scroll} from './events';
|
||||||
export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, Resolve} from './interfaces';
|
export {CanActivate, CanActivateChild, CanDeactivate, CanLoad, Resolve} from './interfaces';
|
||||||
export {DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
|
export {DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
|
||||||
export {NavigationExtras, Router} from './router';
|
export {Navigation, NavigationExtras, Router} from './router';
|
||||||
export {ROUTES} from './router_config_loader';
|
export {ROUTES} from './router_config_loader';
|
||||||
export {ExtraOptions, ROUTER_CONFIGURATION, ROUTER_INITIALIZER, RouterModule, provideRoutes} from './router_module';
|
export {ExtraOptions, ROUTER_CONFIGURATION, ROUTER_INITIALIZER, RouterModule, provideRoutes} from './router_module';
|
||||||
export {ChildrenOutletContexts, OutletContext} from './router_outlet_context';
|
export {ChildrenOutletContexts, OutletContext} from './router_outlet_context';
|
||||||
|
|
|
@ -147,7 +147,7 @@ export interface NavigationExtras {
|
||||||
replaceUrl?: boolean;
|
replaceUrl?: boolean;
|
||||||
/**
|
/**
|
||||||
* State passed to any navigation. This value will be accessible through the `extras` object
|
* State passed to any navigation. This value will be accessible through the `extras` object
|
||||||
* returned from `router.getCurrentTransition()` while a navigation is executing. Once a
|
* returned from `router.getCurrentNavigation()` while a navigation is executing. Once a
|
||||||
* navigation completes, this value will be written to `history.state` when the `location.go`
|
* navigation completes, this value will be written to `history.state` when the `location.go`
|
||||||
* or `location.replaceState` method is called before activating of this route. Note that
|
* or `location.replaceState` method is called before activating of this route. Note that
|
||||||
* `history.state` will not pass an object equality test because the `navigationId` will be
|
* `history.state` will not pass an object equality test because the `navigationId` will be
|
||||||
|
@ -181,6 +181,57 @@ function defaultMalformedUriErrorHandler(
|
||||||
return urlSerializer.parse('/');
|
return urlSerializer.parse('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RestoredState = {
|
||||||
|
[k: string]: any; navigationId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description
|
||||||
|
*
|
||||||
|
* Information about any given navigation. This information can be gotten from the router at
|
||||||
|
* any time using the `router.getCurrentNavigation()` method.
|
||||||
|
*
|
||||||
|
* @publicApi
|
||||||
|
*/
|
||||||
|
export type Navigation = {
|
||||||
|
/**
|
||||||
|
* The ID of the current navigation.
|
||||||
|
*/
|
||||||
|
id: number;
|
||||||
|
/**
|
||||||
|
* Target URL passed into the {@link Router#navigateByUrl} call before navigation. This is
|
||||||
|
* the value before the router has parsed or applied redirects to it.
|
||||||
|
*/
|
||||||
|
initialUrl: string | UrlTree;
|
||||||
|
/**
|
||||||
|
* The initial target URL after being parsed with {@link UrlSerializer.extract()}.
|
||||||
|
*/
|
||||||
|
extractedUrl: UrlTree;
|
||||||
|
/**
|
||||||
|
* Extracted URL after redirects have been applied. This URL may not be available immediately,
|
||||||
|
* therefore this property can be `undefined`. It is guaranteed to be set after the
|
||||||
|
* {@link RoutesRecognized} event fires.
|
||||||
|
*/
|
||||||
|
finalUrl?: UrlTree;
|
||||||
|
/**
|
||||||
|
* Identifies the trigger of the navigation.
|
||||||
|
*
|
||||||
|
* * 'imperative'--triggered by `router.navigateByUrl` or `router.navigate`.
|
||||||
|
* * 'popstate'--triggered by a popstate event
|
||||||
|
* * 'hashchange'--triggered by a hashchange event
|
||||||
|
*/
|
||||||
|
trigger: 'imperative' | 'popstate' | 'hashchange';
|
||||||
|
/**
|
||||||
|
* The NavigationExtras used in this navigation. See {@link NavigationExtras} for more info.
|
||||||
|
*/
|
||||||
|
extras: NavigationExtras;
|
||||||
|
/**
|
||||||
|
* Previously successful Navigation object. Only a single previous Navigation is available,
|
||||||
|
* therefore this previous Navigation will always have a `null` value for `previousNavigation`.
|
||||||
|
*/
|
||||||
|
previousNavigation: Navigation | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type NavigationTransition = {
|
export type NavigationTransition = {
|
||||||
id: number,
|
id: number,
|
||||||
currentUrlTree: UrlTree,
|
currentUrlTree: UrlTree,
|
||||||
|
@ -193,7 +244,7 @@ export type NavigationTransition = {
|
||||||
reject: any,
|
reject: any,
|
||||||
promise: Promise<boolean>,
|
promise: Promise<boolean>,
|
||||||
source: NavigationTrigger,
|
source: NavigationTrigger,
|
||||||
restoredState: {navigationId: number} | null,
|
restoredState: RestoredState | null,
|
||||||
currentSnapshot: RouterStateSnapshot,
|
currentSnapshot: RouterStateSnapshot,
|
||||||
targetSnapshot: RouterStateSnapshot | null,
|
targetSnapshot: RouterStateSnapshot | null,
|
||||||
currentRouterState: RouterState,
|
currentRouterState: RouterState,
|
||||||
|
@ -242,6 +293,8 @@ export class Router {
|
||||||
private rawUrlTree: UrlTree;
|
private rawUrlTree: UrlTree;
|
||||||
private readonly transitions: BehaviorSubject<NavigationTransition>;
|
private readonly transitions: BehaviorSubject<NavigationTransition>;
|
||||||
private navigations: Observable<NavigationTransition>;
|
private navigations: Observable<NavigationTransition>;
|
||||||
|
private lastSuccessfulNavigation: Navigation|null = null;
|
||||||
|
private currentNavigation: Navigation|null = null;
|
||||||
|
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
private locationSubscription !: Subscription;
|
private locationSubscription !: Subscription;
|
||||||
|
@ -387,6 +440,20 @@ export class Router {
|
||||||
...t, extractedUrl: this.urlHandlingStrategy.extract(t.rawUrl)
|
...t, extractedUrl: this.urlHandlingStrategy.extract(t.rawUrl)
|
||||||
} as NavigationTransition)),
|
} as NavigationTransition)),
|
||||||
|
|
||||||
|
// Store the Navigation object
|
||||||
|
tap(t => {
|
||||||
|
this.currentNavigation = {
|
||||||
|
id: t.id,
|
||||||
|
initialUrl: t.currentRawUrl,
|
||||||
|
extractedUrl: t.extractedUrl,
|
||||||
|
trigger: t.source,
|
||||||
|
extras: t.extras,
|
||||||
|
previousNavigation: this.lastSuccessfulNavigation ?
|
||||||
|
{...this.lastSuccessfulNavigation, previousNavigation: null} :
|
||||||
|
null
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
// 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 => {
|
||||||
let completed = false;
|
let completed = false;
|
||||||
|
@ -420,6 +487,15 @@ export class Router {
|
||||||
applyRedirects(
|
applyRedirects(
|
||||||
this.ngModule.injector, this.configLoader, this.urlSerializer,
|
this.ngModule.injector, this.configLoader, this.urlSerializer,
|
||||||
this.config),
|
this.config),
|
||||||
|
|
||||||
|
// Update the currentNavigation
|
||||||
|
tap(t => {
|
||||||
|
this.currentNavigation = {
|
||||||
|
...this.currentNavigation !,
|
||||||
|
finalUrl: t.urlAfterRedirects
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
// Recognize
|
// Recognize
|
||||||
recognize(
|
recognize(
|
||||||
this.rootComponentType, this.config, (url) => this.serializeUrl(url),
|
this.rootComponentType, this.config, (url) => this.serializeUrl(url),
|
||||||
|
@ -617,6 +693,10 @@ export class Router {
|
||||||
eventsSubject.next(navCancel);
|
eventsSubject.next(navCancel);
|
||||||
t.resolve(false);
|
t.resolve(false);
|
||||||
}
|
}
|
||||||
|
// currentNavigation should always be reset to null here. If navigation was
|
||||||
|
// successful, lastSuccessfulTransition will have already been set. Therefore we
|
||||||
|
// can safely set currentNavigation to null here.
|
||||||
|
this.currentNavigation = null;
|
||||||
}),
|
}),
|
||||||
catchError((e) => {
|
catchError((e) => {
|
||||||
errored = true;
|
errored = true;
|
||||||
|
@ -696,9 +776,8 @@ export class Router {
|
||||||
// Navigations coming from Angular router have a navigationId state property. When this
|
// Navigations coming from Angular router have a navigationId state property. When this
|
||||||
// exists, restore the state.
|
// exists, restore the state.
|
||||||
const state = change.state && change.state.navigationId ? change.state : null;
|
const state = change.state && change.state.navigationId ? change.state : null;
|
||||||
setTimeout(() => {
|
setTimeout(
|
||||||
this.scheduleNavigation(rawUrlTree, source, state, null, {replaceUrl: true});
|
() => { this.scheduleNavigation(rawUrlTree, source, state, {replaceUrl: true}); }, 0);
|
||||||
}, 0);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -706,6 +785,9 @@ export class Router {
|
||||||
/** The current url */
|
/** The current url */
|
||||||
get url(): string { return this.serializeUrl(this.currentUrlTree); }
|
get url(): string { return this.serializeUrl(this.currentUrlTree); }
|
||||||
|
|
||||||
|
/** The current Navigation object if one exists */
|
||||||
|
getCurrentNavigation(): Navigation|null { return this.currentNavigation; }
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
triggerEvent(event: Event): void { (this.events as Subject<Event>).next(event); }
|
triggerEvent(event: Event): void { (this.events as Subject<Event>).next(event); }
|
||||||
|
|
||||||
|
@ -849,7 +931,7 @@ export class Router {
|
||||||
const urlTree = isUrlTree(url) ? url : this.parseUrl(url);
|
const urlTree = isUrlTree(url) ? url : this.parseUrl(url);
|
||||||
const mergedTree = this.urlHandlingStrategy.merge(urlTree, this.rawUrlTree);
|
const mergedTree = this.urlHandlingStrategy.merge(urlTree, this.rawUrlTree);
|
||||||
|
|
||||||
return this.scheduleNavigation(mergedTree, 'imperative', null, extras.state || null, extras);
|
return this.scheduleNavigation(mergedTree, 'imperative', null, extras);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -929,14 +1011,16 @@ export class Router {
|
||||||
(this.events as Subject<Event>)
|
(this.events as Subject<Event>)
|
||||||
.next(new NavigationEnd(
|
.next(new NavigationEnd(
|
||||||
t.id, this.serializeUrl(t.extractedUrl), this.serializeUrl(this.currentUrlTree)));
|
t.id, this.serializeUrl(t.extractedUrl), this.serializeUrl(this.currentUrlTree)));
|
||||||
|
this.lastSuccessfulNavigation = this.currentNavigation;
|
||||||
|
this.currentNavigation = null;
|
||||||
t.resolve(true);
|
t.resolve(true);
|
||||||
},
|
},
|
||||||
e => { this.console.warn(`Unhandled Navigation Error: `); });
|
e => { this.console.warn(`Unhandled Navigation Error: `); });
|
||||||
}
|
}
|
||||||
|
|
||||||
private scheduleNavigation(
|
private scheduleNavigation(
|
||||||
rawUrl: UrlTree, source: NavigationTrigger, restoredState: {navigationId: number}|null,
|
rawUrl: UrlTree, source: NavigationTrigger, restoredState: RestoredState|null,
|
||||||
futureState: {[key: string]: any}|null, extras: NavigationExtras): Promise<boolean> {
|
extras: NavigationExtras): Promise<boolean> {
|
||||||
const lastNavigation = this.getTransition();
|
const lastNavigation = this.getTransition();
|
||||||
// If the user triggers a navigation imperatively (e.g., by using navigateByUrl),
|
// If the user triggers a navigation imperatively (e.g., by using navigateByUrl),
|
||||||
// and that navigation results in 'replaceState' that leads to the same URL,
|
// and that navigation results in 'replaceState' that leads to the same URL,
|
||||||
|
@ -990,6 +1074,7 @@ export class Router {
|
||||||
const path = this.urlSerializer.serialize(url);
|
const path = this.urlSerializer.serialize(url);
|
||||||
state = state || {};
|
state = state || {};
|
||||||
if (this.location.isCurrentPathEqualTo(path) || replaceUrl) {
|
if (this.location.isCurrentPathEqualTo(path) || replaceUrl) {
|
||||||
|
// TODO(jasonaden): Remove first `navigationId` and rely on `ng` namespace.
|
||||||
this.location.replaceState(path, '', {...state, navigationId: id});
|
this.location.replaceState(path, '', {...state, navigationId: id});
|
||||||
} else {
|
} else {
|
||||||
this.location.go(path, '', {...state, navigationId: id});
|
this.location.go(path, '', {...state, navigationId: id});
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/
|
||||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
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 {fixmeIvy} from '@angular/private/testing';
|
import {fixmeIvy} from '@angular/private/testing';
|
||||||
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, Navigation, 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, first, map, tap} from 'rxjs/operators';
|
import {filter, first, map, tap} from 'rxjs/operators';
|
||||||
|
|
||||||
|
@ -142,21 +142,22 @@ describe('Integration', () => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const fixture = createRoot(router, RootCmp);
|
const fixture = createRoot(router, RootCmp);
|
||||||
// let transition: NavigationTransitionx = null !;
|
let navigation: Navigation = null !;
|
||||||
// router.events.subscribe(e => {
|
router.events.subscribe(e => {
|
||||||
// if (e instanceof NavigationStart) {
|
if (e instanceof NavigationStart) {
|
||||||
// transition = router.getCurrentTransition();
|
navigation = router.getCurrentNavigation() !;
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
|
|
||||||
router.navigateByUrl('/simple', {state: {foo: 'bar'}});
|
router.navigateByUrl('/simple', {state: {foo: 'bar'}});
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const history = (location as any)._history;
|
const history = (location as any)._history;
|
||||||
expect(history[history.length - 1].state.foo).toBe('bar');
|
expect(history[history.length - 1].state.foo).toBe('bar');
|
||||||
expect(history[history.length - 1].state).toEqual({foo: 'bar', navigationId: history.length});
|
expect(history[history.length - 1].state)
|
||||||
// expect(transition.state).toBeDefined();
|
.toEqual({foo: 'bar', navigationId: history.length});
|
||||||
// expect(transition.state).toEqual({foo: 'bar'});
|
expect(navigation.extras.state).toBeDefined();
|
||||||
|
expect(navigation.extras.state).toEqual({foo: 'bar'});
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should not pollute browser history when replaceUrl is set to true',
|
it('should not pollute browser history when replaceUrl is set to true',
|
||||||
|
@ -1887,8 +1888,7 @@ describe('Integration', () => {
|
||||||
path: 'team/:id',
|
path: 'team/:id',
|
||||||
component: TeamCmp,
|
component: TeamCmp,
|
||||||
children: [
|
children: [
|
||||||
{path: 'link', component: LinkWithState},
|
{path: 'link', component: LinkWithState}, {path: 'simple', component: SimpleCmp}
|
||||||
{path: 'simple', component: SimpleCmp}
|
|
||||||
]
|
]
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
@ -1906,7 +1906,8 @@ describe('Integration', () => {
|
||||||
const history = (location as any)._history;
|
const history = (location as any)._history;
|
||||||
|
|
||||||
expect(history[history.length - 1].state.foo).toBe('bar');
|
expect(history[history.length - 1].state.foo).toBe('bar');
|
||||||
expect(history[history.length - 1].state).toEqual({foo: 'bar', navigationId: history.length});
|
expect(history[history.length - 1].state)
|
||||||
|
.toEqual({foo: 'bar', navigationId: history.length});
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1924,6 +1925,33 @@ describe('Integration', () => {
|
||||||
expect(location.path()).toEqual('/team/22');
|
expect(location.path()).toEqual('/team/22');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
it('should update Navigation object after redirects are applied',
|
||||||
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
let initialUrl, afterRedirectUrl;
|
||||||
|
|
||||||
|
router.resetConfig([
|
||||||
|
{path: 'old/team/:id', redirectTo: 'team/:id'}, {path: 'team/:id', component: TeamCmp}
|
||||||
|
]);
|
||||||
|
|
||||||
|
router.events.subscribe(e => {
|
||||||
|
if (e instanceof NavigationStart) {
|
||||||
|
const navigation = router.getCurrentNavigation();
|
||||||
|
initialUrl = navigation && navigation.finalUrl;
|
||||||
|
}
|
||||||
|
if (e instanceof RoutesRecognized) {
|
||||||
|
const navigation = router.getCurrentNavigation();
|
||||||
|
afterRedirectUrl = navigation && navigation.finalUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.navigateByUrl('old/team/22');
|
||||||
|
advance(fixture);
|
||||||
|
|
||||||
|
expect(initialUrl).toBeUndefined();
|
||||||
|
expect(router.serializeUrl(afterRedirectUrl as any)).toBe('/team/22');
|
||||||
|
})));
|
||||||
|
|
||||||
it('should not break the back button when trigger by location change',
|
it('should not break the back button when trigger by location change',
|
||||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
const fixture = TestBed.createComponent(RootCmp);
|
const fixture = TestBed.createComponent(RootCmp);
|
||||||
|
|
|
@ -149,6 +149,16 @@ export declare type LoadChildren = string | LoadChildrenCallback;
|
||||||
|
|
||||||
export declare type LoadChildrenCallback = () => Type<any> | NgModuleFactory<any> | Promise<Type<any>> | Observable<Type<any>>;
|
export declare type LoadChildrenCallback = () => Type<any> | NgModuleFactory<any> | Promise<Type<any>> | Observable<Type<any>>;
|
||||||
|
|
||||||
|
export declare type Navigation = {
|
||||||
|
id: number;
|
||||||
|
initialUrl: string | UrlTree;
|
||||||
|
extractedUrl: UrlTree;
|
||||||
|
finalUrl?: UrlTree;
|
||||||
|
trigger: 'imperative' | 'popstate' | 'hashchange';
|
||||||
|
extras: NavigationExtras;
|
||||||
|
previousNavigation: Navigation | null;
|
||||||
|
};
|
||||||
|
|
||||||
export declare class NavigationCancel extends RouterEvent {
|
export declare class NavigationCancel extends RouterEvent {
|
||||||
reason: string;
|
reason: string;
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -185,11 +195,15 @@ export interface NavigationExtras {
|
||||||
relativeTo?: ActivatedRoute | null;
|
relativeTo?: ActivatedRoute | null;
|
||||||
replaceUrl?: boolean;
|
replaceUrl?: boolean;
|
||||||
skipLocationChange?: boolean;
|
skipLocationChange?: boolean;
|
||||||
|
state?: {
|
||||||
|
[k: string]: any;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare class NavigationStart extends RouterEvent {
|
export declare class NavigationStart extends RouterEvent {
|
||||||
navigationTrigger?: 'imperative' | 'popstate' | 'hashchange';
|
navigationTrigger?: 'imperative' | 'popstate' | 'hashchange';
|
||||||
restoredState?: {
|
restoredState?: {
|
||||||
|
[k: string]: any;
|
||||||
navigationId: number;
|
navigationId: number;
|
||||||
} | null;
|
} | null;
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -197,6 +211,7 @@ export declare class NavigationStart extends RouterEvent {
|
||||||
url: string,
|
url: string,
|
||||||
navigationTrigger?: 'imperative' | 'popstate' | 'hashchange',
|
navigationTrigger?: 'imperative' | 'popstate' | 'hashchange',
|
||||||
restoredState?: {
|
restoredState?: {
|
||||||
|
[k: string]: any;
|
||||||
navigationId: number;
|
navigationId: number;
|
||||||
} | null);
|
} | null);
|
||||||
toString(): string;
|
toString(): string;
|
||||||
|
@ -316,6 +331,7 @@ export declare class Router {
|
||||||
constructor(rootComponentType: Type<any> | null, urlSerializer: UrlSerializer, rootContexts: ChildrenOutletContexts, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes);
|
constructor(rootComponentType: Type<any> | null, urlSerializer: UrlSerializer, rootContexts: ChildrenOutletContexts, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes);
|
||||||
createUrlTree(commands: any[], navigationExtras?: NavigationExtras): UrlTree;
|
createUrlTree(commands: any[], navigationExtras?: NavigationExtras): UrlTree;
|
||||||
dispose(): void;
|
dispose(): void;
|
||||||
|
getCurrentNavigation(): Navigation | null;
|
||||||
initialNavigation(): void;
|
initialNavigation(): void;
|
||||||
isActive(url: string | UrlTree, exact: boolean): boolean;
|
isActive(url: string | UrlTree, exact: boolean): boolean;
|
||||||
navigate(commands: any[], extras?: NavigationExtras): Promise<boolean>;
|
navigate(commands: any[], extras?: NavigationExtras): Promise<boolean>;
|
||||||
|
@ -358,6 +374,9 @@ export declare class RouterLink {
|
||||||
replaceUrl: boolean;
|
replaceUrl: boolean;
|
||||||
routerLink: any[] | string;
|
routerLink: any[] | string;
|
||||||
skipLocationChange: boolean;
|
skipLocationChange: boolean;
|
||||||
|
state?: {
|
||||||
|
[k: string]: any;
|
||||||
|
};
|
||||||
readonly urlTree: UrlTree;
|
readonly urlTree: UrlTree;
|
||||||
constructor(router: Router, route: ActivatedRoute, tabIndex: string, renderer: Renderer2, el: ElementRef);
|
constructor(router: Router, route: ActivatedRoute, tabIndex: string, renderer: Renderer2, el: ElementRef);
|
||||||
onClick(): boolean;
|
onClick(): boolean;
|
||||||
|
@ -389,6 +408,9 @@ export declare class RouterLinkWithHref implements OnChanges, OnDestroy {
|
||||||
replaceUrl: boolean;
|
replaceUrl: boolean;
|
||||||
routerLink: any[] | string;
|
routerLink: any[] | string;
|
||||||
skipLocationChange: boolean;
|
skipLocationChange: boolean;
|
||||||
|
state?: {
|
||||||
|
[k: string]: any;
|
||||||
|
};
|
||||||
target: string;
|
target: string;
|
||||||
readonly urlTree: UrlTree;
|
readonly urlTree: UrlTree;
|
||||||
constructor(router: Router, route: ActivatedRoute, locationStrategy: LocationStrategy);
|
constructor(router: Router, route: ActivatedRoute, locationStrategy: LocationStrategy);
|
||||||
|
|
Loading…
Reference in New Issue