Hiding and Showing Vue Components Based on Properties

Posted by Jason Lewis in Vue on

So this is probably going to be a very use-case specific article on how you can hide and show components, or anything really, based on a property value. That's actually probably not even the best way to describe what I want to talk about. I'm pretty new to the Vue scene, so take what I say with a grain of salt and feel free to point out something I could've done better in the comments.

Anyhow, I recently stumbled upon a problem. I have two components, an Alert.vue compnent and an ErrorAlert.vue component. The alert component is responsible for displaying a Foundation callout that can vary in color, as well as a close button if it's an important alert, and then the body of the alert. It looks something like this:

<template>
    <div v-show="show" class="callout {{ type }}" transition="remove">
        <button v-if="important" class="close-button" aria-label="Close alert" type="button" @click="show = false">
            <span aria-hidden="true">&times;</span>
        </button>

        <slot></slot>
    </div>
</template>

<script>
    export default {
        props: {
            type: {
                default: 'alert'
            },
            important: {
                type: Boolean,
                default: true
            },
            show: {
                type: Boolean,
                default: true
            }
        },

        data() {
            return {}
        }
    }
</script>

<style>
    .remove-transition {
        transition: opacity 0.4s ease;
    }

    .remove-leave, .remove-enter {
        opacity: 0;
    }
</style>

We have our template, which contains the required divs and classes. Notice that we have a v-show on the containing div which will be responsible for hiding/showing the component. The show property defaults to true, so the alert will be displayed. It also has a transition attribute and we've named the transition remove.

Then we have our script section, which simply sets up the properties for the component.

And finally we have the styles for the remove transition. It's just a simple fade.

Right, so this is main alert component. Pretty straightforward stuff. You could include this component in a page like this.

<alert type="info" important>This is just an information message.</alert>

And when you loaded your page you'd be able to hide the alert by clicking the close button. So that's all good. No worries there. But now let's take a look at the error alert component.

Here is what it looks like.

<template>
    <alert important>
        <h5>Woops, looks like something went wrong.</h5>

        <ul>
            <li v-for="error in errors">{{ error }}</li>
        </ul>
    </alert>
</template>

<script>
    import Alert from './Alert.vue'

    export default {
        components: {
            Alert
        },
        props: {
            errors: {},
            important: {
                type: Boolean,
                default: true
            }
        },
        data() {
            return {}
        }
    }
</script>

This time we're going to make use of the already existing alert component. Inside the alert we are using v-for on a list to loop over an errors object that's defined as a property on the component. We can use this component like so.

<error-alert :errors="errors"></error-alert>

In this case we are binding our errors object to the error alerts errors property. Remember, this is only one-way binding. So when we make a POST request and it fails, we'd populate the errors object with the errors encountered and they would also be changed on the error alerts errors property.

So the problem here is that when we use this component in one of our pages, we don't want it to be shown straight away. The use case for this component is on forms where we show some server-side validation errors. So, how can we tell the alert component that we want to hide it from our error alert component?

Two-way binding.

This wasn't immediately obvious to me, but it's a pretty neat solution I think. What we will do is add a show property to our error alert component, except the default this time will be false.

props: {
    show: {
        type: Boolean,
        default: false
    }
}

And now we need to bind it to the alert. It would look something like this.

<alert important :show.sync="show">

The .sync indicates that we want to two-way bind the show property from the error alert component to the show property on the alert component.

So now when the error alert component is used it will be hidden by default. But, how exactly do we get it to show up for the first time?

We watch.

Vue is able to watch properties on your components for changes. The solution here is to watch the errors property, which we know will be updated with any errors we get from our server. And all we want to do is set the errors property to true or false depending on whether or not we have any errors to show. Something like this.

watch: {
    errors(newVal) {
        this.$set('show', Object.getOwnPropertyNames(newVal).length > 1);
    }
}

The reason I'm using Object.getOwnPropertyNames is because I want to get an array of all the keys inside the object. Then we get the length of that array and make sure it's more than 1. The reason why we check for more than 1 is because the property will also have the __ob__ property, which is an Observable instance. So if we have more than 1 key in the object we know we have some errors, so we set show to true. And because we have two-way binding for the show property, this change will also occur on the alert component.

And that's about all there is to it. The error alert can now be hidden and shown on the same page several times by simply watching the errors property, and then updating a two-way binded property. Hope this gives you some ideas, and as I said before, feel free to point out anything I could've done better!