Knowledge Required: A few hours of VueJS

Tools required: VueJS

When you have a page or component that loads dynamic data (such as from an API), it’s important to communicate to the end user that the your page is busy loading content. It can also be used as a protection mechanism for making sure users can’t do actions until you’re ready. For example, if you have a component which updates user details, you first want to ensure that you’ve loaded the user details into the page before allowing them the opportunity to submit any updates.

Basic VueJS component

To start with, let’s create the new component which is going to contain our content. Within the component we’ll use the VueJS data hook to locally store variables within the component. Within our component data, we’re going to create some variables that we can use can use as boolean switches, indicating to VueJS when to display the loader section or when to display loaded content.

<template>

    <!--Our loader section-->
    <div>
        <p>{{this.loading_msg}}</p>
    </div>

    <!--Our loaded content section-->
    <div>
        <p>This is the final content </p>
        <img id="myImg" ref="myImg" src="/default.png">
    </div>

</template>

<script>
    export default {
        name: "MyComponent",
        data () {
            return {
                page_busy: true,
                loading_msg: "The page is loading"
            }
        }
    }
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>    

From here, our objectives for this component are the following:

  • When the page is busy we want the div containing our loading message to show
  • When the page is not busy, we show our content and hide the loading message
  • During the loading phase, set the <img> element in our component to a dynamic source based on an API response

v-if

v-if is a function included in VueJS which determines if the HTML element is rendered on the page. They key concept to understand is that v-if works by adding that element to the DOM when the supplied condition is true. This helps to make VueJS flexible and efficient because you don’t add unnecessary elements to the DOM, meaning there’s less elements on the page for the browser to process and keep track of. For example, for the div that contains our loading message, we can use v-if to add it to the DOM when our page is busy and remove it when the page isn’t busy. Using our existing HTML div element, we can do this by modifying the loading div:

    <div  v-if="this.page_busy"> 
        <p>{{this.loading_msg}}</p>
    </div>

Note that you within your template the this is used as context to address variables stored within our data under the <script> tags.

By toggling page_busy to true and false, we can display our loading message to the user.

When to use v-if

v-ifs are helpful for static content. In this scenario we’re unlikely to ever change our loading message after it’s been displayed to the user. However, for the image in our content section, we may want to change the image source based on an API request. If the content section div is controlled by a v-if that is false, when we want to change the image source we won’t be able to. Why? We can see the <img> element in our file but the browser has no concept of the element because v-if will not add it to the DOM. That means after we do our API request where you’d traditionally change the image source with something like document.getElementById('myImg') you’ll get an error because the image is not in the DOM for the browser to access. If you’re confused by this, the W3Schools explanation here is extremely helpful.

Instead, we can use CSS to add elements to the DOM but make them invisible to the user. This way we can still access them at any point from JavaScript within our component.

Use classes and tenaries to make items invisible

In order to make things invisible in CSS, we can use the display option. Lets make a class under our style tag to do this and set it not to display anything which has the invisible class.

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.invisible { 
    display: none; 
}
</style>

Now if we apply this CSS class to any element, it will be added to the DOM but the user will be none the wiser. However, we want to dynamically apply our invisibility to hide page content when we’re doing something like loading content from an API request.

Sounds like an if statement!

Ternaries are used in JavaScript to shorthand if statements instead of requiring large code blocks. This is super helpful when we want to minmize the length of the VueJS property for our HTML component. We can use v-bind:class VueJS property to determine when we should apply our invisible class. Let’s apply this to our content div now:

    <!--Our content section-->
    <div v-bind:class="this.page_busy' ? 'invisible' : undefined ">
        <p>This is the final content </p>
        <img id="myImg" ref="myImg" src="/default.png">
    </div>

To summarize , when our variable page_busy is true then the class will be invisible, else the class will be undefined (no extra classes will be applied).

In full written form, our tenary would look like:

if (page_busy) {
    'invisible'
} else {
    undefined
}

Bringing it all together

By default, when MyComponent is added to our VueJS app, the loading message will be displayed because we’ve set the default value of page_busy to true in our data. Our goal is to do an API request and then change the source of our <img> element based on the response. To do this, we will use the mounted hook in VueJS. The mounted hook occurs when our component gets added to our VueJS App and will only be run once. More information about life cycle hooks for VueJS 3 can be found here.

A mounted hook will need to be added to the component under the <script> tag. Within the mounted hook, we can add any JavaScript we want to execute when the component is added. In this case, I’ve shown an example of using JavaScript’s fetch API to retrieve an API response. We use promise syntax to set the <img> source after we’ve completed the request:


    export default {
        name: "PageTitle",
        data () {
            return {
                page_busy: true,
                loading_msg: "The page is loading"

            }
        }, 
        mounted() {
            this.page_busy = true; // ensure we're hiding content
            
            // use the fetch API to get a response
            fetch('/my_api/img_source', {
                method: 'GET',
            })
            .then((response) => {
                // convert the response to JSON
                return response.json()
            })
            .then((response) => {
                // set our image element source based on the field 'img_src' in our API resp
                document.getElementById('myImg').src = response['img_src']

            })
            .catch((e) => {
                const error = "API error: " + e
                console.error(error)
            });

        }
    }

Conclusion

Now you’ve got a basic working example of how to flip between a loading screen and main content for a component. I recommend using CSS to make transitions between the between the two smoother. Perhaps including a spinner or similar could also communicate to the user that loading is occurring. There are some good examples of how to make spinners in pure CSS here.

By now you should have a better understanding of VueJS reactivity with v-if and v-bind:class and know when is best to apply each.

EOF break