Did you know that the router service in Ember provides everything we need to recreate the LinkTo
component?
Let's create a component called Link
with the same public API as LinkTo
.
ember g component Link -gc
Next, we'll add some links to our application template.
<Link @route="index">
Home
</Link>
<Link @route="about" class="custom-class">
About
</Link>
<Link @route="posts" @query=>
Posts in descending order
</Link>
<Link @route="posts.post" @model=>
Post 1
</Link>
To begin the implementation of the Link
component, let's start with the template:
<a
href=
...attributes
>
</a>
In this component, we'll have a getter called href
that will use the router service and the arguments passed into the component (@route
, @model
, and @query
), to determine the URL. More on this below.
I've added a click handler that will ultimately use the router service and call transitionTo
to this.href
.
Lastly, I added ...attributes
so that HTML attributes passed into the component will end up on the rendered anchor, like the class="custom-class"
above.
Now let's look at the backing class.
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
export default class LinkComponent extends Component {
@service router;
get href() {
const args = [this.args.route];
if ("model" in this.args) {
args.push(this.args.model);
}
if ("query" in this.args) {
args.push({
queryParams: this.args.query,
});
}
return this.router.urlFor(...args);
}
@action
handleClick(event) {
event.preventDefault();
this.router.transitionTo(this.href);
}
}
The href
getter uses the router service's urlFor
method to determine the href
of the anchor based upon the arguments passed into the component, like @model
and @query
.
The handleClick
action simply calls transitionTo
on the router service with the URL computed in the href
getter. I think it is more common to see transitionTo
being called with the route name but it also works by passing in the computed URL, and this is a documented behavior.
And that's it for a basic implementation!
Now let's add support for the active
class. Add the following isActive
getter to the backing class.
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
export default class LinkComponent extends Component {
@service router;
get href() {
const args = [this.args.route];
if ('model' in this.args) {
args.push(this.args.model);
}
if ('query' in this.args) {
args.push({
queryParams: this.args.query,
});
}
return this.router.urlFor(...args);
}
get isActive() { const args = [this.args.route]; if ('model' in this.args) { args.push(this.args.model); } return this.router.isActive(...args); }
@action
handleClick(event) {
event.preventDefault();
this.router.transitionTo(this.href);
}
}
And finally we'll update the template to use isActive
.
<a
href=
class=
...attributes
>
</a>
Now our anchors will have class="active"
when the route is active.
Conclusion
While I recommend just using the LinkTo
component since it probably handles some edge cases that I'm not aware of, creating a custom LinkTo
component can be useful in some scenarios, like if you want to add in some logic before and after calling transitionTo
.