diff --git a/README.md b/README.md
index c29bc60e..6ff9f171 100644
--- a/README.md
+++ b/README.md
@@ -118,6 +118,23 @@ You can also mix and match the parameter styles, however you like.
{{/let}}
```
+##### `fromURL`
+
+Instead of the positional & named link parameters described above, you can also
+create a `Link` instance from a serialized URL.
+
+```hbs
+{{! someURL = "/blogs/tech/posts/dont-break-the-web" }}
+{{#let (link fromURL=this.someURL) as |l|}}
+
+ Read the next great post.
+
+{{/let}}
+```
+
+`fromURL` is mutually exclusive with the other link parameters: `route`, `model`
+& `models`, `query`
+
### Parameters
In addition to the parameters shown above, the `{{link}}` helper also accepts a
@@ -280,6 +297,25 @@ Query Params object.
{{/link-to}}
```
+##### `@fromURL`
+
+Optional. Mutually exclusive with [`@route`](#route), [`@model`](#model) /
+[`@models`](#models), [`@query`](#query).
+
+**Example**
+
+```hbs
+
+
+ Click me
+
+
+```
+
##### `@preventDefault`
Optional. Default: `true`
diff --git a/addon/components/link/template.hbs b/addon/components/link/template.hbs
index dce2be7c..6a8050fd 100644
--- a/addon/components/link/template.hbs
+++ b/addon/components/link/template.hbs
@@ -4,6 +4,7 @@
models=@models
model=@model
query=@query
+ fromURL=@fromURL
preventDefault=@preventDefault
)
~}}
\ No newline at end of file
diff --git a/addon/helpers/link.ts b/addon/helpers/link.ts
index 8ba823f9..665958b6 100644
--- a/addon/helpers/link.ts
+++ b/addon/helpers/link.ts
@@ -22,6 +22,14 @@ export interface LinkHelperNamedParams
* Optional shortcut for `models={{array model}}`.
*/
model?: RouteModel;
+
+ /**
+ * Instead of any of the other `LinkParams` used to construct a
+ * `LinkInstance`, you can also provide a serialized URL instead.
+ *
+ * This is mutually exclusive with any other `LinkParams`.
+ */
+ fromURL?: string;
}
export default class LinkHelper extends Helper {
@@ -47,6 +55,20 @@ export default class LinkHelper extends Helper {
!('routeName' in named)
);
+ if (named.fromURL) {
+ assert(
+ `When specifying a serialized 'fromURL' ('${named.fromURL}'), you can't provide any further 'LinkParams'.`,
+ !([
+ 'route',
+ 'models',
+ 'model',
+ 'query'
+ ] as (keyof LinkHelperNamedParams)[]).some(name => named[name])
+ );
+
+ return this.linkManager.getLinkPramsFromURL(named.fromURL);
+ }
+
assert(
`Either pass the target route name as a positional parameter ('${positional[0]}') or pass it as a named parameter ('${named.route}').`,
!(positional[0] && named.route)
diff --git a/addon/services/link-manager.ts b/addon/services/link-manager.ts
index 643507e1..4a0a91f7 100644
--- a/addon/services/link-manager.ts
+++ b/addon/services/link-manager.ts
@@ -1,5 +1,6 @@
import { action } from '@ember/object';
import { addListener, removeListener } from '@ember/object/events';
+import RouteInfo from '@ember/routing/-private/route-info';
import Transition from '@ember/routing/-private/transition';
import RouterService from '@ember/routing/router-service';
import Service from '@ember/service';
@@ -8,6 +9,10 @@ import { tracked } from '@glimmer/tracking';
import Link, { LinkParams, UILinkParams, UILink } from '../link';
+interface RouterServiceWithRecognize extends RouterService {
+ recognize(url: string): RouteInfo;
+}
+
export default class LinkManagerService extends Service {
@tracked
private _currentTransitionStack?: Transition[];
@@ -16,7 +21,7 @@ export default class LinkManagerService extends Service {
* The `RouterService` instance to be used by the generated `Link` instances.
*/
@service('router')
- readonly router!: RouterService;
+ readonly router!: RouterServiceWithRecognize;
/**
* Whether the router has been initialized.
@@ -50,6 +55,29 @@ export default class LinkManagerService extends Service {
return new UILink(this, { ...linkParams, ...uiParams });
}
+ /**
+ * Deserializes the `LinkParams` to be passed to `createLink` / `createUILink`
+ * from a URL.
+ *
+ * If the URL cannot be recognized by the router, an error is thrown.
+ */
+ getLinkPramsFromURL(url: string): LinkParams {
+ const routeInfo = this.router.recognize(url);
+ return LinkManagerService.getLinkParamsFromRouteInfo(routeInfo);
+ }
+
+ /**
+ * Converts a `RouteInfo` object into `LinkParams`.
+ */
+ static getLinkParamsFromRouteInfo(routeInfo: RouteInfo): LinkParams {
+ const models = routeInfo.paramNames.map(name => routeInfo.params[name]!);
+ return {
+ route: routeInfo.name,
+ query: routeInfo.queryParams,
+ models
+ };
+ }
+
constructor(properties?: object) {
super(properties);
diff --git a/tests/acceptance/link-test.ts b/tests/acceptance/link-test.ts
index b48338ce..b1c78db6 100644
--- a/tests/acceptance/link-test.ts
+++ b/tests/acceptance/link-test.ts
@@ -421,20 +421,19 @@ module('Acceptance | link', function(hooks) {
);
});
- test('toString()', async function(this: TestContext, assert) {
+ test('fromURL', async function(this: TestContext, assert) {
this.Router.map(function() {
- this.route('foo');
- this.route('bar');
+ this.route('foo', { path: 'foo/:id' });
});
this.owner.register(
'template:application',
- hbs`{{get (link "foo" query=(hash bar=(link "bar"))) "url"}}`
+ hbs`{{get (link fromURL="/foo/123?bar=qux") "url"}}`
);
await visit('/');
assert.equal(currentURL(), '/');
- assert.dom().hasText('/foo?bar=%2Fbar');
+ assert.dom().hasText('/foo/123?bar=qux');
});
});