How to use #link-to in a top-level route AND nested routes

I am using ember-concurrency to do a look-ahead search in my glossary app. Everything was working fine until I added nested routes like so:

Router.map(function() {
  this.route("home", { path: "/" });
  this.route("terms", function() {
    this.route("letter", { path: "/:letter" }, function() {
      this.route("term_id", { path: "/:term_id" });
    });
  });
});

Upon adding them, the search still worked in every route but every time I clicked a link I got:

WARNING: This link is in an inactive loading state because at least one of its models currently has a null/undefined value, or the provided route name is invalid.

At the time I was still using handlebars for the #link-to, which looked like this.

    <input type="text" oninput={{perform searchRepo value="target.value"}} class="border ml-10">

    {{#if searchRepo.isRunning}}
      {{loading-spinner}}
    {{/if}}

    <ul class="md:ml-30m my-5">
      {{#each (sort-by "term" searchRepo.lastSuccessful.value) as |term|}}
        {{#link-to "terms" term.letter.id}}{{term.term}}{{/link-to}}<br>
      {{/each}}
    </ul>

After several hours of Googling and debugging, I decided to try using angle brackets like this.

      {{#each (sort-by "term" searchRepo.lastSuccessful.value) as |term|}}
        <LinkTo @route="terms.letter.term_id" @model={{term}} class="text-blue-light text-xs leading-normal pt-4 no-underline">{{term.term}}</LinkTo><br>
      {{/each}}

It appeared to work and I was ready to call it a night feeling good about myself for fixing a problem all by myself :man_student: … sadly, that feeling did not last long as I quickly realized the search worked in terms/:letter and terms/:letter/:term_id, but was broken in home. :sob:

After a couple more hours of Googling variations on but did not pass the models required for generating its dynamic segments and Cannot read property 'shouldSupercede' of undefined, I noticed an even more perplexing issue. When performing a search on the home route, console only threw this error the first time you searched in the route. After clearing the field and performing a second search in the home route, the search was successful but did not show-up on the screen.

I realize this might be pretty hard to visualize, so here is a 36 second video demonstrating the problem.

Check out this Video

Check out this video: https://embed.vidyard.com/share/tzsMXWC1g7z98eWQJH2ZyC

I spent a bit longer trying current-when and finally had to shut it down in frustration.

Does anyone have an answer on how to make this work?

Unfortunately a lot of the errors around link-to can be confusing so it can be difficult to tell what’s wrong in situations like this. The easiest place to start is usually looking at your router:

Router.map(function() {
  this.route("home", { path: "/" });
  this.route("terms", function() {
    // this.route('index') - this is implicit but it is here
    this.route("letter", { path: "/:letter" }, function() {
      // this.route('index') - this is implicit but it is here
      this.route("term_id", { path: "/:term_id" });
    });
  });
});

level 0

So at the top level you have a route called terms that has no dynamic segments.

level 1: terms

Under terms you have two child routes:

  • index (which is implicit)
  • letter (explicit) - no “static segments” and one dynamic segment called “letter”

level 2: letter

Under your “letter” route you have two routes:

  • index (which is implicit)
  • term_id (explicit) - no “static segments” and one dynamic segment called “term_id”

Because of this you know that the following link-tos are valid:

  • "terms" links to terms.index
  • "terms.letter" <some letter> links to term.letter
  • "terms.letter.term_id" <some letter> <some term id> links to term.letter.term_id

This can get a little confusing too since if you’re on one of the routes with a dynamic segment already you don’t need to provide something for its dynamic segment. (like if you were already on terms.letter you wouldn’t have to provide the “letter” dynamic segment, though you still could).

Personally I prefer always providing the dynamic segments. Further I always like providing ids instead of models as dynamic segments, because if you provide a model some of the route code doesn’t run.

So… what does this mean for you? Well in your application template your code should look like:

    <ul class="md:ml-30m my-5">
      {{#each (sort-by "term" searchRepo.lastSuccessful.value) as |term|}}
        {{#link-to "terms.letter.term_id" term.letter term.id}}{{term.term}}{{/link-to}}<br>
      {{/each}}
    </ul>

The key is providing both dynamic segments that this route requires (letter and term_id) as we illustrated above.

I will note a couple other things:

  • i noticed that your “letter” code is nested under terms/index which does not match your router and will not work as expected. You should move the “letter” directory from /app/pods/terms/index/letter to just /app/pods/terms/letter
  • you don’t need to name the “term” route “term_id” if you don’t want to. It won’t hurt anything if you do but it could be this.route("term", { path: "/:term_id" }); instead just to be concise. Note that if you do change it your link-to would then be {{link-to "terms.letter.term" ...}} instead
1 Like

My hero!! Damn man, you’re always there for me when I need it most!! Thank you!! You need any help with digital marketing, SEO, SEM, social media marketing, content marketing, CRMs, or CSS/digital design? I gotta find a way to repay all these favors you do for me. You like going out? My man DJ ROCK CITY is always doing it real big out in Chicago. Maybe I can get him to hook you up on his next party.

On the code side, looks like I forgot to commit after @balint pointed out the index issue yesterday. Though I’m kind of glad it was there because you helped clarify what I was still a little cloudy on after fixing the problem. So sure enough, your solution works out-of-the box. YUGE!

I’m curious, can you help me understand why this works on home even though we didn’t add it to the {{#link-to}}? Is it for the same implicit index reason?

Ha well I’m happy to try and help, I’ve learned a ton myself trying to answer questions here, and obviously received a lot from the framework and community so anything I can do to give back is hardly scratching the surface of my “debt” as it were. Really appreciate the offer, not working on anything that requires your broad palette of skills at the moment but maybe in the future I’ll take you up on that :smile:. And DJ ROCK CITY sounds pretty awesome too but my party days are mostly over now that I have kids and am old enough I have to sleep now :joy:

As for why this works on home and not application… that’s a good question. Probably not related to index… and since home doesn’t have any dynamic segments it’s probably not related to that… my only guess is that the code was subtly different between the two and the code in “home” worked? Or maybe since you were using angle brackets in home the angle brackets handle the args a little more gracefully? That’s just a guess though :man_shrugging:

1 Like