How to dynamically render <router-link> or <a> in Vue depending on whether the link is exrternal or internal?

Viewed 7066
<template>
  <component
    :is="type === 'internal' ? 'router-link' : 'a'"
    :to="type === 'internal' ? link : null"
    :href="type !== 'internal' ? link : null"
  >
    <slot />
  </component>
</template>

<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";

@Component
export default class SiteLink extends Vue {
  @Prop({
    validator: (value: string) =>
      ["external", "internal"].includes(value)
  })
  private readonly type!: string;

  @Prop({ type: String })
  private readonly link!: string;
}
</script>

Above is a Vue component where it will render a link. I have stripped out anything not relevant to the problem (I.e. rel, target, class, etc.).

Understanding - My understanding of Vue Router is that <router-link to="/about">About</router-link> and <a href="/about">About</a> will both render as<a href="/about">About</a> in the DOM, with a difference being the <router-link> version will give the link the SPA functionality (I.e. doesn't load a new page, it dynamically renders a component).

Expected - When type="internal", it will render the <router-link> version. When type="external", it will render the <a> version.

<site-link type="external" link="https://stackoverflow.com">Stack Overflow</site-link>

Will render

<a href="https://stackoverflow.com">Stack Overflow</a>
<site-link type="internal" link="/about">About</site-link>

Will render

<router-link to="/about">About</router-link>

Which is then handle by VueRouter to render

<a href="/about">About</a>

Actual - When type="internal", a <a> with no href rendered in the DOM. When type="external", it renders as expected.

<site-link type="external" link="https://stackoverflow.com">Stack Overflow</site-link>

Will render

<a href="https://stackoverflow.com">Stack Overflow</a>
<site-link type="internal" link="/about">About</site-link>

Will render

<router-link to="/about">About</router-link>

Which is then handle by VueRouter to render

<a>About</a> <!-- Notice there is no href -->

Any ideas how I can achieve what I want?

3 Answers

The better and cleaner approach:

  <router-link v-if="type === 'internal' :to="link">
    <slot />
  </router-link>
  <a v-else :ref="link"> <slot /> </a>

You can use v-if in root element so it solves you the case

Or may be you just missed path part?

  <component
    :is="type === 'internal' ? 'router-link' : 'a'"
    :to="type === 'internal' ? { path: link } : null"
    :href="type !== 'internal' ? link : null"
  >
    <slot />
  </component>

...with a difference being the <router-link> version will give the link the SPA functionality (I.e. doesn't load a new page, it dynamically renders a component).

By not loading a new page, I suppose you mean it does not reload the page. And yes, it doesn't because the onclick handler actually gets assigned to a function that does a preventDefault (preventing page redirection) while pushing a new entry into the history stack.

If you take a look at the API reference, the most noticeable thing <router-link> does for you is it toggles between the active-classes depending on the active/current route.

So, that being said, you could do your dynamic <a>nchor rendering inside the default slot via the v-slot; because at this point, the href slot prop would have been a resolved URL which you can then safely bind to the DOM href attribute.


Edit

Added an example (untested).

<router-link
  :to="link"
  v-slot="{ href, route, navigate, isActive, isExactActive }">
  <a
    :class="isActive ? 'your-custom-class' : 'anything'"
    :href="type !== 'internal' ? href : 'javascript:void(0);'"
    @click="customNavigate(navigate.bind($event))">
    <slot></slot>
  </a>
</router-link>

Where the customNavigate handler could be something like:

{
  methods: {
    customNavigate(navigate) {
      if (this.type === 'internal') {
        navigate();
        return false;
      }
    }
  }
}

You can basically add any attributes on the in-component anchor tag based on the slot props, like navigating in certain ways, adding special classes, depending on your use case.

The official documentation includes an example of how to do this now.

Their approach is to create a custom template that handles external links.

Related