Search code examples
vue.jsvuejs2components

Conditional wrapper rendering in vue


I'm making a link/button component which either can have a button or an anchor wrapper, a text and an optional icon. My template code below is currently rendering either an anchor or a button (with the exact same content) based on an if statement on the wrapper element, resulting in duplicate code.

<template>
    <a v-if="link" v-bind:href="url" class="btn" :class="modifier" :id="id" role="button" :disabled="disabled">
        {{buttonText}}
        <svg class="icon" v-if="icon" :class="iconModifier">
            <use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
        </svg>
    </a>
    <button v-else type="button" class="btn" :class="modifier" :id="id" :disabled="disabled">
        {{buttonText}}
        <svg class="icon" v-if="icon" :class="iconModifier">
            <use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
        </svg>
    </button>
</template>

Is there a more clean way for wrapping my buttonText and icon inside either an anchor or button?


Solution

  • I've solved my issue by intensive Google-ing! Found this issue regarding Vue on Github which pointed me in the right direction.

    Small piece of backstory

    I'm using Vue in combination with Storybook to build a component library in which a button can either be a button or an anchor. All buttons look alike (apart from color) and can be used for submitting or linking. To keep my folder structure ordered, I would like a solution that generates a multiple buttons types (with or without link) from one single file.

    Solution

    Using computed properties I'm able to "calculate" the necessary tag, based on the url property of my component. When a url is passed, I know that my button has to link to another page. If there is no url property it should submit something or preform a custom click handler (not in the sample code below).

    I've created the returnComponentTag computed property to avoid placing any complex or bulky logic (like my original solution) in my template. This returns either an a or a button tag based on the existence of the url property.

    Next, as suggested by ajobi, using the :is attribute I'm able to define the component tag based on the result of my computed property. Below a stripped sample of my final (and working) solution:

    <template>
        <component :is="returnComponentTag" v-bind:href="url ? url : ''" class="btn" :class="modifier" :id="id">
            {{buttonText}}
        </component>
    </template>
    
    <script>
    export default {
        name: "Button",
        props: {
            id: {
                type: Number
            },
            buttonText: {
                type: String,
                required: true,
                default: "Button"
            },
            modifier: {
                type: String,
                default: "btn-cta-01"
            },
            url: {
                type: String,
                default: ""
            }
        }, 
        computed: {
            returnComponentTag() {
                return this.url ? "a" : "button"
            }
        }
    };
    </script>