ember-link
Introduces a new Link
primitive to pass around self-contained references to
routes, like URLs, but with state (isActive
, ...) and methods (transitionTo
,
...). Also brings along an accompanying template helper and component for easy
usage in templates.
ember-link
does to routing whatember-concurrency
did to asynchrony!
Installation
ember install ember-link
Usage
{{link}}
Helper
The {{link}}
helper returns a UILink
instance.
Invocation Styles
Positional Parameters
Named Parameters
When passing a single model, you can use model
instead of models
:
Mix & Match
You can also mix and match the parameter styles, however you like.
fromURL
Instead of the positional & named link parameters described above, you can also
create a Link
instance from a serialized URL.
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
preventDefault
default parameter. It defaults to true
and intelligently
prevents hard browser transitions when clicking <a>
elements.
See @preventDefault
and UILink
.
💡 Pro Tips
Instead of using the {{#let}}
helper, you can use the
<Link>
component to achieve the same scoping effect, with
subjectively nicer syntax.
Even better yet, make Link
/ UILink
a first-class
primitive in your app architecture! Instead of manually wiring up
Link#url
and Link#transitionTo()
every time, rather
create your own ready-to-use, style-guide-compliant link and button components
that accept @link
as an argument instead of @href
and @onClick
.
This is akin to the popular async task button component concept.
<Link>
Component
Similar to the the {{link}}
helper, the <Link>
component
yields a UILink
instance.
Arguments
@route
Required.
The target route name.
Example
{{link-to}}
equivalent
@models
Optional. Mutually exclusive with @model
.
An array of models / dynamic segments.
Example
{{link-to}}
equivalent
@model
Optional. Mutually exclusive with @models
.
Shorthand for providing a single model / dynamic segment. The following two invocations are equivalent:
@query
Optional.
Query Params object.
Example
{{link-to}}
equivalent
@fromURL
Optional. Mutually exclusive with @route
, @model
/
@models
, @query
.
Example
@preventDefault
Optional. Default: true
If enabled, the transitionTo
and
replaceWith
actions will try to call
event.preventDefault()
on the first argument, if it is an
event. This is an anti-foot-gun to make <Link>
just work<a>
and
<button>
, which would otherwise trigger a native browser navigation / form
submission.
Yielded Parameters
The <Link>
component yields a UILink
instance.
url
string
The URL for this link that you can pass to an <a>
tag as the href
attribute.
isActive
boolean
Whether this route is currently active, including potentially supplied models and query params.
In the following example, only one link will be is-active
at any time.
isActiveWithoutQueryParams
boolean
Whether this route is currently active, including potentially supplied models, but ignoring query params.
In the following example, the first two links will be is-active
simultaneously.
isActiveWithoutModels
boolean
Whether this route is currently active, but ignoring models and query params.
In the following example, both links will be is-active
simultaneously.
transitionTo()
(event?: Event) => Transition
Transition into the target route.
If @preventDefault
is enabled, also calls event.preventDefault()
.
replaceWith()
(event?: Event) => Transition
Transition into the target route while replacing the current URL, if possible.
If @preventDefault
is enabled, also calls event.preventDefault()
.
Link
A Link
is a self-contained reference to a concrete route, including models and
query params. It's basically like a
<LinkTo>
/ {{link-to}}
component you can pass around.
You can create a Link
via the LinkManager
service.
UILink
extends Link
with some anti-foot-guns and conveniences. It
can also be created via the LinkManager
service, but also via
the {{link}}
helper and <Link>
component.
Properties
isActive
Type: boolean
Whether this route is currently active, including potentially supplied models and query params.
isActiveWithoutQueryParams
Type: boolean
Whether this route is currently active, including potentially supplied models, but ignoring query params.
isActiveWithoutModels
Type: boolean
Whether this route is currently active, but ignoring models and query params.
url
Type: string
The URL for this link that you can pass to an <a>
tag as the href
attribute.
routeName
Type: string
The target route name of this link.
models
Type: RouteModel[]
The route models passed in this link.
queryParams
Type: Record<string, unknown> | undefined
The query params for this link, if specified.
Methods
transitionTo()
Returns: Transition
Transition into the target route.
replaceWith()
Returns: Transition
Transition into the target route while replacing the current URL, if possible.
UILink
UILink
extends Link
with anti-foot-guns and conveniences. This
class is meant to be used in templates, primarily through <a>
& <button>
elements.
It wraps transitionTo()
and replaceWith()
to optionally accept an event
argument. It will intelligently
- call
event.preventDefault()
to prevent hard page reloads - open the page in a new tab, when
Cmd
/Ctrl
clicking
It can be created via the LinkManager
service, but also via
the {{link}}
helper and <Link>
component.
LinkManager
The LinkManager
service is used by the {{link}} helper
and
<Link>
component to create UILink
instances.
You can also use this service directly to programmatically create link references.
createLink(linkParams: LinkParams): Link
Returns: Link
interface LinkParams {
/**
* The target route name.
*/
route: string;
/**
* Optional array of models / dynamic segments.
*/
models?: RouteModel[];
/**
* Optional query params object.
*/
query?: QueryParams;
}
createUILink(linkParams: LinkParams, uiParams: UILinkParams): UILink
Returns: UILink
interface UILinkParams {
/**
* Whether or not to call `event.preventDefault()`, if the first parameter to
* the `transitionTo` or `replaceWith` action is an `Event`. This is useful to
* prevent links from accidentally triggering real browser navigation or
* buttons from submitting a form.
*
* Defaults to `true`.
*/
preventDefault?: boolean;
}
getLinkParamsFromURL(url: string): LinkParams
Returns: LinkParams
Use this method to derive LinkParams
from a serialized, recognizable URL, that
you can then pass into createLink
/ createUILink
.
Testing
In acceptance / application tests (setupApplicationTest(hooks)
)
your app boots with a fully-fledged router, so ember-link
just works normally.
In integration / render tests (setupRenderingTest(hooks)
) the
router is not initialized, so ember-link
can't operate normally. To still
support using {{link}}
& friends in render tests, you can use the
setupLink(hooks)
test helper.
import { click, render } from '@ember/test-helpers';
import { setupRenderingTest } from 'ember-qunit';
import { module, test } from 'qunit';
import { setupLink, linkFor, TestLink } from 'ember-link/test-support';
import hbs from 'htmlbars-inline-precompile';
module('`setupLink` example', function (hooks) {
setupRenderingTest(hooks);
setupLink(hooks);
test('`<Link>` component works in render tests', async function (assert) {
await render(hbs`
<Link @route="some.route" as |l|>
<a
href={{l.url}}
class={{if l.isActive "is-active"}}
{{on "click" l.transitionTo}}
>
Click me
</a>
</Link>
`);
const link = linkFor('some.route');
link.onTransitionTo = assert.step('link clicked');
await click('a');
assert.verifySteps(['link clicked']);
});
});
Related RFCs / Projects
ember-engine-router-service
: Allows you to useember-link
inside enginesember-router-helpers
- RFC 391 "Router Helpers"
- RFC 339 "Router link component and routing helpers"
- RFC 459 "Angle Bracket Invocations For Built-in Components"