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 -gcNext, 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.

