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,