Robert Kusznier
Robert Kusznier

Reputation: 6911

How to make router-link work with custom link component?

<base-link> component

I have a Vue component <base-link>, which I use every time I want to have an achor. It's mostly for applying styles specific to links, so that all the links across the whole page look the same without applying global styles.

Make <router-link> use <base-link>

When using <router-link> component to create a link, I cannot apply those styles (<base-link> styles are scoped) unless <router-link> uses my <base-link> component to create the anchor element.

Fortunately <router-link> provides tag attribute, which seems to do exactly that. Unfortunately I can't get it to work. I have 2 problems:

  1. All my components are locally registered (I use ES6 modules with Webpack and import components locally every time I need them). <router-link> doesn't know what <base-link> component is and can't render it. Is there a way to inject a local component for <router-link> to use?

  2. To solve problem #1, I thought it's enough to declare <base-link> component globally. Unfortunately it still doesn't work. This time <base-link> component gets rendered properly, but is still not functional - doesn't react to click events. It seems to me the problem is that it's href attribute isn't set at all. Is there a way to make <router-link> set it properly? (without setting it manually)

Question

How do I solve problems #1 and #2? I suspect #1 might be not possible, but I hope at least #2 is.

Code example

Here is a pen with code below, which illustrates both problems.

const router = new VueRouter({
  routes: [
    {
      path: "/",
      component: {
        template: '<p>Homepage template</p>'
      }
    },
    {
      path: "/subpage",
      component: {
        template: '<p>Subpage template</p>'
      }
    }
  ]
});

// Globally registered BaseLink.
Vue.component('BaseLinkGlobal', {
  props: {
    href: String
  },
  template: `
    <a
      :href="href"
      class="BaseLinkGlobal"
    >
      <slot />
    </a>
  `
})

const vue = new Vue({
  el: "#app",
  router,
  components: {
    // Locally registered BaseLink.
    BaseLinkLocal: {
      props: {
        href: String
      },
      template: `
        <a
          :href="href"
          class="BaseLinkLocal"
        >
          <slot />
        </a>
      `
    }
  },
  template: `
    <div>
      <!-- 2 router links. One uses locally registered BaseLink
        -- and the other one a globally registered one. -->
      <nav>
        <router-link
          to="/"
          tag="base-link-local"
        >
          Home
        </router-link>
        <router-link
          to="/subpage"
          tag="base-link-global"
        >
          Subpage
        </router-link>
      </nav>
      <router-view />
    </div>
  `
});

Upvotes: 1

Views: 4177

Answers (2)

Vamsi Krishna
Vamsi Krishna

Reputation: 31362

You can create a base link component which can double as a normal a tag or <router-link> when you wish.

//Base link

<template>
  <component :is="type" :class="{'base': type === 'a'}" v-bind="$attrs">
    <slot></slot>
  </component>
</template>

<script>
export default {
  props: {
    routerLink: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    type() {
      return this.routerLink ? 'router-link' : 'a'
    }
  }
}
</script>

<style scopex>
.base {
  border: 1px solid red;
}
</style>

Usage

when you want to use it as a normal link just do not provide the router-link prop as below:

<base-link>This is a base a tag</base-link>

To use it as a router-link just add the router-link prop along with the to prop:

<base-link router-link to="/">This is router-link</base-link>

Explanation about the base-link component:

  • We use a component which is provided by vuejs to render a tag or router-link base on the truthiness of the routerLink prop.

  • A class of .base is added if it is a normal link i.e a

  • we bind $attrs which allows us to make the component more transparent i.e allows us to use attributes like href or to without passing them as props.

     <base-link href="https://google.com">go to google</base-link>
    

You can have a look here for more explanation about usage of $attrs

Upvotes: 2

Jacob Goh
Jacob Goh

Reputation: 20845

This is for solving problem #2

The global component doesn't inherit the event listener of the router link. You can make it inherit by adding v-on="$listeners" to the global component.

// Globally registered BaseLink.
Vue.component('BaseLinkGlobal', {
  props: {
    href: String
  },
  template: `
    <a
      :href="href"
      class="BaseLinkGlobal"
      v-on="$listeners"
    >
      <slot />
    </a>
  `
})

The link works after adding it: https://codepen.io/jacobgoh101/pen/YvqJxL?editors=0010

Upvotes: 1

Related Questions