diff --git a/packages/router/src/directives/router_link.ts b/packages/router/src/directives/router_link.ts index 1a05911dc8..8b68e093f8 100644 --- a/packages/router/src/directives/router_link.ts +++ b/packages/router/src/directives/router_link.ts @@ -77,6 +77,27 @@ import {UrlTree} from '../url_tree'; * * ``` * + * You can provide a `state` value to be persisted to the browser's History.state + * property (See https://developer.mozilla.org/en-US/docs/Web/API/History#Properties). It's + * used as follows: + * + * ``` + * + * link to user component + * + * ``` + * + * And later the value can be read from the router through `router.getCurrentTransition. + * For example, to capture the `tracingId` above during the `NavigationStart` event: + * + * ``` + * // Get NavigationStart events + * router.events.pipe(filter(e => e instanceof NavigationStart)).subscribe(e => { + * const transition = router.getCurrentTransition(); + * tracingService.trace({id: transition.extras.state}); + * }); + * ``` + * * The router link directive always treats the provided input as a delta to the current url. * * For instance, if the current url is `/user/(box//aux:team)`. @@ -104,6 +125,7 @@ export class RouterLink { @Input() skipLocationChange !: boolean; // TODO(issue/24571): remove '!'. @Input() replaceUrl !: boolean; + @Input() state?: {[k: string]: any}; private commands: any[] = []; // TODO(issue/24571): remove '!'. private preserve !: boolean; @@ -185,6 +207,7 @@ export class RouterLinkWithHref implements OnChanges, OnDestroy { @Input() skipLocationChange !: boolean; // TODO(issue/24571): remove '!'. @Input() replaceUrl !: boolean; + @Input() state?: {[k: string]: any}; private commands: any[] = []; private subscription: Subscription; // TODO(issue/24571): remove '!'. @@ -237,6 +260,7 @@ export class RouterLinkWithHref implements OnChanges, OnDestroy { const extras = { skipLocationChange: attrBoolValue(this.skipLocationChange), replaceUrl: attrBoolValue(this.replaceUrl), + state: this.state }; this.router.navigateByUrl(this.urlTree, extras); return false; diff --git a/packages/router/test/integration.spec.ts b/packages/router/test/integration.spec.ts index 70dd0bf3d7..2cd8b2bd3f 100644 --- a/packages/router/test/integration.spec.ts +++ b/packages/router/test/integration.spec.ts @@ -1878,6 +1878,36 @@ describe('Integration', () => { expect(location.path()).toEqual('/team/22/simple?q=1#f'); }))); + + it('should support history state', + fakeAsync(inject([Router, Location], (router: Router, location: SpyLocation) => { + const fixture = createRoot(router, RootCmp); + + router.resetConfig([{ + path: 'team/:id', + component: TeamCmp, + children: [ + {path: 'link', component: LinkWithState}, + {path: 'simple', component: SimpleCmp} + ] + }]); + + router.navigateByUrl('/team/22/link'); + advance(fixture); + + const native = fixture.nativeElement.querySelector('a'); + expect(native.getAttribute('href')).toEqual('/team/22/simple'); + native.click(); + advance(fixture); + + expect(fixture.nativeElement).toHaveText('team 22 [ simple, right: ]'); + + // Check the history entry + const history = (location as any)._history; + + expect(history[history.length - 1].state.foo).toBe('bar'); + expect(history[history.length - 1].state).toEqual({foo: 'bar', navigationId: history.length}); + }))); }); describe('redirects', () => { @@ -4576,6 +4606,13 @@ class RelativeLinkCmp { class LinkWithQueryParamsAndFragment { } +@Component({ + selector: 'link-cmp', + template: `link` +}) +class LinkWithState { +} + @Component({selector: 'simple-cmp', template: `simple`}) class SimpleCmp { } @@ -4770,6 +4807,7 @@ class LazyComponent { RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, + LinkWithState, CollectParamsCmp, QueryParamsAndFragmentCmp, StringLinkButtonCmp, @@ -4797,6 +4835,7 @@ class LazyComponent { RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, + LinkWithState, CollectParamsCmp, QueryParamsAndFragmentCmp, StringLinkButtonCmp, @@ -4826,6 +4865,7 @@ class LazyComponent { RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, + LinkWithState, CollectParamsCmp, QueryParamsAndFragmentCmp, StringLinkButtonCmp,