Add Extra Actions to LinkTo in Octane

Viewed 62

I have a dropdown menu with links, when the links are clicked I'd like the menu to close.

Something like (a11y/i18n truncated):

<ul class="menu">
    <li>
        <LinkTo @route="myprofile">
            Profile
        </LinkTo>
    </li>
    <li>
        <LinkTo @route="logout">
            Logout
        </LinkTo>
    </li>
</ul>

I'd like to add an additional click handler to the link to, something like:

<ul class="menu">
    <li>
        <LinkTo @route="myprofile" {{on "click" this.closeMenu}}>
            Profile
        </LinkTo>
    </li>
    <li>
        <LinkTo @route="logout" {{on "click" this.closeMenu}}>
            Logout
        </LinkTo>
    </li>
</ul>

However this makes the LinkTo useless as it reloads the page as if following a link instead of transitioning to a new route. We're currently doing this using hember-link-action, but I'd love to find a more idiomatic way to approach this problem.

2 Answers

If you need to perform additional logic, you may implement redirect in an action instead of using the LinkTo helper. To do so, you need to inject RouterService into your component and then call its transitionTo method. Something like:

export default class ExampleComponent extends Component {
  @service router;

  @action
  navigate(route) {
    this.menuExpanded = false;
    this.router.transitionTo(route);
  }
}

Note that there also exist the transitionTo() method from Route and transitionToRoute() from Controller that behave like the LinkTo helper. But those methods are deprecated now, and using RouterService is a recommended idiomatic way of doing transitions in js code.

I've written a component to mostly handle this, but I'm quite certain there are more edge cases in LinkTo then I've covered (for example it doesn't cover a passed model or list of models). I called this <LinkToWithAction /> and it looks like:

<a href={{this.href}} class={{if this.isActive "active"}} {{on "click" this.navigate}} >
    {{yield}}
</a>
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';

export default class LinkToWithActionComponent extends Component {
  @service router;

  get href() {
    return this.router.urlFor(this.args.route, {
      queryParams: this.queryParams,
    });
  }

  get isActive() {
    return this.router.isActive(this.args.route);
  }

  get queryParams() {
    return this.args.queryParams ?? {};
  }

  @action
  navigate(evt) {
    evt.preventDefault();
    this.args.action();
    this.router.transitionTo(this.args.route, {
      queryParams: this.queryParams,
    });
  }
}

and it is called as:

<LinkToWithAction
  @route="mymaterials"
  @action={{set this.isOpen false}}
  @queryParams={{hash course=null}}
>
  {{t "general.myprofile"}}
</LinkToWithAction>

This is made more annoying by this issue with transitionTo that adds unset queryParams to the URL when called which effects the public router service. The built in component uses the private internal router where this behavior doesn't exist, and it may be worth using that private service, but for now we're going to live with passing the query params.

Related