Feather icons usage in Vue.JS
Asked Answered
A

2

5

I've been trying to use feather-icons in a new vue project. I first initialized the project using the vue-clie tools:

vue init webpack

once done, I ran:

npm install
npm run dev

After that I installed feather-icons through npm as follows:

npm install --save feather-icons

Once done, I tried using the icons by importing the module in my main.js file:

main.js:

import 'feather-icons'

import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: {
    App
  }
})

Then I tried using an icon in a Hello component:

Hello.vue:

<template>
  <div>
    <h1> Hello World!</h1>
    <i data-feather="flag"></i>
  </div>
</template>

<script>
  export default {
    name: 'hello',
    data() {
      return {
        msg: 'Welcome to Your Vue.js App'
      }
    }
  }

</script>
<style>
</style>

No error is detected during execution, yet the icon set refuses to work. I've tried including the feather-icons directly on the index.html file, but the problem persists.

I'm guessing whatever is causing this is related to the data-feather attribute on the i tag required to invoke feather-icons.

I've been at it for almost a couple hours, but nothing I tried seems to work. Any help would be appreciated. Thanks.

UPDATE 1: As per @yuriy636's suggestion, I imported feather-icons in my App component then called feather.replace() in mounted:

App.vue:

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>

  import feather from 'feather-icons'

  export default {
    name: 'app',

    mounted() {
      feather.replace();
    }
  }

</script>

<style>
</style>

UPDATE 2:

As pointed out by @smarx, there is a module named vue-feather-icons that facilitates the usage of feather-icons with vue. just install it, import it and use it. This seems to have solved the issue.

Afterdamp answered 6/7, 2017 at 22:20 Comment(6)
Try calling feather.replace()(well, for that you have to name the feather import) as stated at the docs github.com/colebemis/feather#quick-start. You could place that in the mounted hook.Platitude
@Platitude Thank you very much, your solution worked. I did as you said and imported feather in my App.vue component then invoked the replace method in mounted. I will edit my original post to reflect the changes. Thanks a lot!Afterdamp
FYI, you may want to use github.com/mage3k/vue-feather-icon instead. It seems like that's easier to integrate with Vue than trying to call feather.replace at the right times. (I think the mounted hook won't be sufficient if the component ever updates?)Rogerrogerio
(Updates the icon, that is.)Rogerrogerio
@smarx Thanks for the suggestion. I was aware of the vue-feathers-icon module but I wanted to figure out what was wrong with my code first before trying it out. I gave it a shot and it's pretty neat. Thanks again.Afterdamp
For others who might stumble upon this, once you've called feather.replace, the original element is gone, so Vue is unable to update it. You can't, e.g., do something like <i v-bind:data-feather="iconName"></i> and expect the icon to update. (This applies to just using feather-icons directly. vue-feather-icon is presumably well integrated with Vue.)Rogerrogerio
R
13

Summarizing the comment thread and presenting another solution for posterity:

  1. The issue with the original code is that there's a missing call to feather.replace, which finds all the elements with data-feather attributes and replaces them with the appropriate icon's SVG.
  2. Calling feather.replace in the mounted hook is good enough for simple use cases, but it doesn't allow for icons changing. (In general, it strikes me as a bad idea to let non-Vue code modify the HTML you're using Vue to render.) E.g., <i v-bind:data-feather="iconName"></i> isn't going to allow for subsequent updates.
  3. vue-feather-icon appears to be a project that integrates better with Vue.

Below is a better way to use "vanilla" feather-icons without running into the above issues. Here, we're dynamically updating the HTML content of a div with the appropriate icon SVG by using a computed property that calls feather.toSvg:

<template>
  <div id="app">
    <h1>{{ message }}</h1>
    <div v-html="iconSvg"></div>
    <button @click="clicky">Click me</button>
  </div>
</template>

<script>
import feather from 'feather-icons'

export default {
  name: 'app',
  data: function () {
    return {
      message: 'Hello, World!',
      icon: 'flag'
    }
  },
  computed: {
    iconSvg: function () {
      return feather.toSvg(this.icon)
    }
  },
  methods: {
    clicky: function () {
      this.message = 'clicked'
      this.icon = 'circle'
    }
  }
}
</script>
Rogerrogerio answered 6/7, 2017 at 23:47 Comment(1)
Thanks! I just want to add that feather.toSvg() is deprecated, we should use feather.icons[name].toSvg() insteadZoophobia
C
0

This can also be done as a functional component, and you can use the icon-names to simply pick which svg to render.

You can also swap out feathers and use another svg-sprite

// Usage    
    <div class="flex items-center p-2 mt-2 bg-white">
      <x-svg icon="log-out" class="w-4 h-4" />
    </div>

 // x-svg.vue
    <template functional>
      <svg
        fill="none"
        :viewBox="props.viewBox"
        :class="['stroke-' + props.stroke, data.class, data.staticClass]"
        class="inline-flex w-4 h-4 text-gray-500 stroke-current hover:text-gray-900 group-hover:text-gray-900"
        stroke-linecap="round"
        stroke-linejoin="round"
        :ref="data.ref"

        :style="[data.style, data.staticStyle]"
        v-bind="data.attrs"
        v-on="listeners"
      >
        <use :href="require('@/assets/img/feather-sptite.svg') + '#' + props.icon" />
      </svg>
    </template>

    <script>
    export default {
      props: {
        icon: {
          type: String,
          default: 'alert-circle'
        },
        stroke: {
          type: Number,
          default: 1,
          validator(v) {
            const sizes = [0.5, 1, 1.5, 2, 2.5]
            return sizes.includes(v)
          }
        },
        viewBox: {
          type: String,
          default: '0 0 24 24'
        }
      }
    }
    </script>
Claus answered 1/3, 2020 at 11:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.