@@ -147,7 +147,7 @@ export interface NavigationExtras {
147147 replaceUrl ?: boolean ;
148148 /**
149149 * State passed to any navigation. This value will be accessible through the `extras` object
150- * returned from `router.getCurrentTransition ()` while a navigation is executing. Once a
150+ * returned from `router.getCurrentNavigation ()` while a navigation is executing. Once a
151151 * navigation completes, this value will be written to `history.state` when the `location.go`
152152 * or `location.replaceState` method is called before activating of this route. Note that
153153 * `history.state` will not pass an object equality test because the `navigationId` will be
@@ -181,6 +181,57 @@ function defaultMalformedUriErrorHandler(
181181 return urlSerializer . parse ( '/' ) ;
182182}
183183
184+ export type RestoredState = {
185+ [ k : string ] : any ; navigationId : number ;
186+ } ;
187+
188+ /**
189+ * @description
190+ *
191+ * Information about any given navigation. This information can be gotten from the router at
192+ * any time using the `router.getCurrentNavigation()` method.
193+ *
194+ * @publicApi
195+ */
196+ export type Navigation = {
197+ /**
198+ * The ID of the current navigation.
199+ */
200+ id : number ;
201+ /**
202+ * Target URL passed into the {@link Router#navigateByUrl} call before navigation. This is
203+ * the value before the router has parsed or applied redirects to it.
204+ */
205+ initialUrl : string | UrlTree ;
206+ /**
207+ * The initial target URL after being parsed with {@link UrlSerializer.extract()}.
208+ */
209+ extractedUrl : UrlTree ;
210+ /**
211+ * Extracted URL after redirects have been applied. This URL may not be available immediately,
212+ * therefore this property can be `undefined`. It is guaranteed to be set after the
213+ * {@link RoutesRecognized} event fires.
214+ */
215+ finalUrl ?: UrlTree ;
216+ /**
217+ * Identifies the trigger of the navigation.
218+ *
219+ * * 'imperative'--triggered by `router.navigateByUrl` or `router.navigate`.
220+ * * 'popstate'--triggered by a popstate event
221+ * * 'hashchange'--triggered by a hashchange event
222+ */
223+ trigger : 'imperative' | 'popstate' | 'hashchange' ;
224+ /**
225+ * The NavigationExtras used in this navigation. See {@link NavigationExtras} for more info.
226+ */
227+ extras : NavigationExtras ;
228+ /**
229+ * Previously successful Navigation object. Only a single previous Navigation is available,
230+ * therefore this previous Navigation will always have a `null` value for `previousNavigation`.
231+ */
232+ previousNavigation : Navigation | null ;
233+ } ;
234+
184235export type NavigationTransition = {
185236 id : number ,
186237 currentUrlTree : UrlTree ,
@@ -193,7 +244,7 @@ export type NavigationTransition = {
193244 reject : any ,
194245 promise : Promise < boolean > ,
195246 source : NavigationTrigger ,
196- restoredState : { navigationId : number } | null ,
247+ restoredState : RestoredState | null ,
197248 currentSnapshot : RouterStateSnapshot ,
198249 targetSnapshot : RouterStateSnapshot | null ,
199250 currentRouterState : RouterState ,
@@ -242,6 +293,8 @@ export class Router {
242293 private rawUrlTree : UrlTree ;
243294 private readonly transitions : BehaviorSubject < NavigationTransition > ;
244295 private navigations : Observable < NavigationTransition > ;
296+ private lastSuccessfulNavigation : Navigation | null = null ;
297+ private currentNavigation : Navigation | null = null ;
245298
246299 // TODO(issue/24571): remove '!'.
247300 private locationSubscription ! : Subscription ;
@@ -387,6 +440,20 @@ export class Router {
387440 ...t , extractedUrl : this . urlHandlingStrategy . extract ( t . rawUrl )
388441 } as NavigationTransition ) ) ,
389442
443+ // Store the Navigation object
444+ tap ( t => {
445+ this . currentNavigation = {
446+ id : t . id ,
447+ initialUrl : t . currentRawUrl ,
448+ extractedUrl : t . extractedUrl ,
449+ trigger : t . source ,
450+ extras : t . extras ,
451+ previousNavigation : this . lastSuccessfulNavigation ?
452+ { ...this . lastSuccessfulNavigation , previousNavigation : null } :
453+ null
454+ } ;
455+ } ) ,
456+
390457 // Using switchMap so we cancel executing navigations when a new one comes in
391458 switchMap ( t => {
392459 let completed = false ;
@@ -420,6 +487,15 @@ export class Router {
420487 applyRedirects (
421488 this . ngModule . injector , this . configLoader , this . urlSerializer ,
422489 this . config ) ,
490+
491+ // Update the currentNavigation
492+ tap ( t => {
493+ this . currentNavigation = {
494+ ...this . currentNavigation ! ,
495+ finalUrl : t . urlAfterRedirects
496+ } ;
497+ } ) ,
498+
423499 // Recognize
424500 recognize (
425501 this . rootComponentType , this . config , ( url ) => this . serializeUrl ( url ) ,
@@ -617,6 +693,10 @@ export class Router {
617693 eventsSubject . next ( navCancel ) ;
618694 t . resolve ( false ) ;
619695 }
696+ // currentNavigation should always be reset to null here. If navigation was
697+ // successful, lastSuccessfulTransition will have already been set. Therefore we
698+ // can safely set currentNavigation to null here.
699+ this . currentNavigation = null ;
620700 } ) ,
621701 catchError ( ( e ) => {
622702 errored = true ;
@@ -696,16 +776,18 @@ export class Router {
696776 // Navigations coming from Angular router have a navigationId state property. When this
697777 // exists, restore the state.
698778 const state = change . state && change . state . navigationId ? change . state : null ;
699- setTimeout ( ( ) => {
700- this . scheduleNavigation ( rawUrlTree , source , state , null , { replaceUrl : true } ) ;
701- } , 0 ) ;
779+ setTimeout (
780+ ( ) => { this . scheduleNavigation ( rawUrlTree , source , state , { replaceUrl : true } ) ; } , 0 ) ;
702781 } ) ;
703782 }
704783 }
705784
706785 /** The current url */
707786 get url ( ) : string { return this . serializeUrl ( this . currentUrlTree ) ; }
708787
788+ /** The current Navigation object if one exists */
789+ getCurrentNavigation ( ) : Navigation | null { return this . currentNavigation ; }
790+
709791 /** @internal */
710792 triggerEvent ( event : Event ) : void { ( this . events as Subject < Event > ) . next ( event ) ; }
711793
@@ -849,7 +931,7 @@ export class Router {
849931 const urlTree = isUrlTree ( url ) ? url : this . parseUrl ( url ) ;
850932 const mergedTree = this . urlHandlingStrategy . merge ( urlTree , this . rawUrlTree ) ;
851933
852- return this . scheduleNavigation ( mergedTree , 'imperative' , null , extras . state || null , extras ) ;
934+ return this . scheduleNavigation ( mergedTree , 'imperative' , null , extras ) ;
853935 }
854936
855937 /**
@@ -929,14 +1011,16 @@ export class Router {
9291011 ( this . events as Subject < Event > )
9301012 . next ( new NavigationEnd (
9311013 t . id , this . serializeUrl ( t . extractedUrl ) , this . serializeUrl ( this . currentUrlTree ) ) ) ;
1014+ this . lastSuccessfulNavigation = this . currentNavigation ;
1015+ this . currentNavigation = null ;
9321016 t . resolve ( true ) ;
9331017 } ,
9341018 e => { this . console . warn ( `Unhandled Navigation Error: ` ) ; } ) ;
9351019 }
9361020
9371021 private scheduleNavigation (
938- rawUrl : UrlTree , source : NavigationTrigger , restoredState : { navigationId : number } | null ,
939- futureState : { [ key : string ] : any } | null , extras : NavigationExtras ) : Promise < boolean > {
1022+ rawUrl : UrlTree , source : NavigationTrigger , restoredState : RestoredState | null ,
1023+ extras : NavigationExtras ) : Promise < boolean > {
9401024 const lastNavigation = this . getTransition ( ) ;
9411025 // If the user triggers a navigation imperatively (e.g., by using navigateByUrl),
9421026 // and that navigation results in 'replaceState' that leads to the same URL,
@@ -990,6 +1074,7 @@ export class Router {
9901074 const path = this . urlSerializer . serialize ( url ) ;
9911075 state = state || { } ;
9921076 if ( this . location . isCurrentPathEqualTo ( path ) || replaceUrl ) {
1077+ // TODO(jasonaden): Remove first `navigationId` and rely on `ng` namespace.
9931078 this . location . replaceState ( path , '' , { ...state , navigationId : id } ) ;
9941079 } else {
9951080 this . location . go ( path , '' , { ...state , navigationId : id } ) ;
0 commit comments