Skip to content

Commit

Permalink
feat(router): Add info property to NavigationExtras
Browse files Browse the repository at this point in the history
This commit adds a property to the navigation options to allow
developers to provide transient navigation info that is available for
the duration of the navigation. This information can be retrieved at any
time with `Router.getCurrentNavigation()!.extras.info`. Previously,
developers were forced to either create a service to hold information
like this or put it on the `state` object, which gets persisted to the
session history.

This feature was partially motivated by the [Navigation API](https://github.com/WICG/navigation-api#example-using-info)
and would be something we would want/need to have feature parity if/when the
Router supports managing navigations with that instead of `History`.
  • Loading branch information
atscott committed Nov 30, 2023
1 parent 1940280 commit 7c940cf
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 0 deletions.
25 changes: 25 additions & 0 deletions packages/router/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1269,4 +1269,29 @@ export interface NavigationBehaviorOptions {
*
*/
state?: {[k: string]: any};

/**
* Use this to convey transient information about this particular navigation, such as how it
* happened. In this way, it's different from the persisted value `state` that will be set to
* `history.state`.
*
* One example of how this might be used is to trigger different single-page navigation animations
* depending on how a certain route was reached. For example, consider a photo gallery app, where
* you can reach the same photo URL and state via various routes:
*
* - Clicking on it in a gallery view
* - Clicking
* - "next" or "previous" when viewing another photo in the album
* - Etc.
*
* Each of these wants a different animation at navigate time. This information doesn't make sense
* to store in the persistent URL or history entry state, but it's still important to communicate
* from the rest of the application, into the router.
*
* This information could be used in coordination with the View Transitions feature and the
* `onViewTransitionCreated` callback. The information might be used in the callback to set
* classes on the document in order to control the transition animations and remove the classes
* when the transition has finished animating.
*/
info?: unknown;
}
2 changes: 2 additions & 0 deletions packages/router/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ export class Router {
const mergedTree =
this.urlHandlingStrategy.merge(e.url, currentTransition.currentRawUrl);
const extras = {
// Persist transient navigation info from the original navigation request.
info: currentTransition.extras.info,
skipLocationChange: currentTransition.extras.skipLocationChange,
// The URL is already updated at this point if we have 'eager' URL
// updates or if the navigation was triggered by the browser (back
Expand Down
42 changes: 42 additions & 0 deletions packages/router/test/integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,48 @@ describe('Integration', () => {
expectEvents(events, []);
});

it('should set transient navigation info', async () => {
let observedInfo: unknown;
const router = TestBed.inject(Router);
router.resetConfig([
{
path: 'simple',
component: SimpleCmp,
canActivate: [() => {
observedInfo = coreInject(Router).getCurrentNavigation()?.extras?.info;
return true;
}]
},
]);

await router.navigateByUrl('/simple', {info: 'navigation info'});
expect(observedInfo).toEqual('navigation info');
});

it('should make transient navigation info available in redirect', async () => {
let observedInfo: unknown;
const router = TestBed.inject(Router);
router.resetConfig([
{
path: 'redirect',
component: SimpleCmp,
canActivate: [() => coreInject(Router).parseUrl('/simple')]
},
{
path: 'simple',
component: SimpleCmp,
canActivate: [() => {
observedInfo = coreInject(Router).getCurrentNavigation()?.extras?.info;
return true;
}]
},
]);

await router.navigateByUrl('/redirect', {info: 'navigation info'});
expect(observedInfo).toBe('navigation info');
expect(router.url).toEqual('/simple');
});

it('should ignore empty paths in relative links',
fakeAsync(inject([Router], (router: Router) => {
router.resetConfig([{
Expand Down

0 comments on commit 7c940cf

Please sign in to comment.