Categories
Vue 3

Add a Loading Spinner to a Vue App with the Vue Loading Overlay Component

Sometimes, we want to show a loading spinner in our Vue app to indicate the app is loading.

In this article, we’ll look at how to add a loading spinner to a Vue app with the Vue Loading Overlay component.

Installation

To install the package, we can run:

npm i vue-loading-overlay

with NPM.

Usage

Once we installed it, we can use it by writing:

<template>
  <div>
    <loading
      :active.sync="isLoading"
      :can-cancel="true"
      :on-cancel="onCancel"
      :is-full-page="fullPage"
    >
    </loading>
    <button @click.prevent="doAjax">fetch Data</button>
  </div>
</template>

<script>
import Loading from "vue-loading-overlay";
import "vue-loading-overlay/dist/vue-loading.css";

export default {
  data() {
    return {
      isLoading: false,
      fullPage: true,
    };
  },
  components: {
    Loading,
  },
  methods: {
    doAjax() {
      this.isLoading = true;
      setTimeout(() => {
        this.isLoading = false;
      }, 5000);
    },
    onCancel() {
      console.log("User cancelled the loader.");
    },
  },
};
</script>

We import the Loading component and register it in the components property.

Also, we import the CSS file to add the spinner styles.

Then we add the loading component into the template.

active is set to the condition when the spinner shows.

can-cancel set to true means we can close the spinner manually.

on-cancel is a function that runs when the spinner closes.

is-full-page is set the condition when the spinner will take the full page.

In the methods property object, we add the doAjax method to set this.isLoading to control when the spinner shows.

onCancel is the method we run when we close the spinner.

We can also use the library as a plugin.

For instance, we can write:

<template>
  <form @submit.prevent="submit" style="height: 500px" ref="formContainer">
    <button type="submit">Login</button>
  </form>
</template>

<script>
import Vue from "vue";
import Loading from "vue-loading-overlay";
import "vue-loading-overlay/dist/vue-loading.css";
Vue.use(Loading);

export default {
  data() {
    return {
      fullPage: false,
    };
  },
  methods: {
    submit() {
      const loader = this.$loading.show({
        container: this.$refs.formContainer,
        canCancel: true,
        onCancel: this.onCancel,
      });
      setTimeout(() => {
        loader.hide();
      }, 5000);
    },
    onCancel() {
      console.log("User cancelled the loader.");
    },
  },
};
</script>

We have the form with a ref assigned to it.

The form element will be used as the container for the spinner.

Then we import the modules and CSS in the same way.

Next, we call Vue.use to register the plugin.

Then we have the submit method that calls this.$loading.show to show the loading spinner,

container is set to the form element which is stored in this.$refs.formContainer .

canCancel sets whether we can close the spinner or not.

onCancel is set to the method that we want to run when we close the spinner.

So when we click Login, we see the spinner displayed for 5 seconds or until we click on it.

Conclusion

We can add a loading spinner into our Vue app with Vue Loading Overlay.

Categories
Vue 3

How to Use the Vue 3 Suspense Component and Create Async Components

To load components only when they’re needed, we can load components asynchronously.

We can also use the Suspense component to render components with a fallback message that’s displayed when it’s loading.

In this article, we’ll look at how to create async components and use the Suspense component to render them.

Vue 3 Suspense Component and Create Async Components

The Vue 3 Suspense has the default slot to render the dynamic components we want.

The fallback slot lets us display a fallback message that’s rendered when the component is loading.

We can define async components with the defineAsyncComponent function.

To use them together, we can write the following:

main.js

import { createApp, defineAsyncComponent } from "vue";
import App from "./App.vue";

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const app = createApp(App);
const HelloWorldAsync = defineAsyncComponent(async () => {
  await sleep(2000);
  return import("./components/HelloWorld.vue");
});

app.component("hello-world", HelloWorldAsync);

app.mount("#app");

App.vue

<template>
  <Suspense>
    <template #default>
      <hello-world />
    </template>
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

<script>
export default {
  name: "App",
};
</script>

components/HelloWorld.vue

<template>
  <div class="hello">hello world</div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
};
</script>

In the main.js file, we have the sleep function to simulate a delay in loading the component.

Then we create the Vue app with the createApp function.

Next, we call defineAsyncComponenty function that takes a function that returns a promise that resolves a component.

We call sleep to add a 2000 ms delay.

Then we return the promise with the component, which we get with the import function with the path to the component.

Then we register the component with the app.component method.

Next, in App.vue , we add the Suspense component with the default slot having the main content we want to display.

And the fallback slot having the content that’s displayed when the component in the default slot is loading.

HelloWorld.vue has the content we want to display.

As a result, we should see the ‘Loading…’ message displayed for 2 seconds and then see ‘hello world’ displayed after that.

Conclusion

We can use the Vue 3 Suspense component with async components to load components dynamically in an async manner.

The Suspense component lets us display a loading message for the user when the async component is being loaded.

Categories
Vue 3

How to Register a Global Component in Vue 3?

Sometimes, we may want to add components to our Vue 3 app that’s available throughout the app.

In this case, global components are suitable for this purpose.

In this article, we’ll look at how to register a global component with Vue 3.

Register a Global Component in Vue 3

To register global components with Vue 3, we can use the app.comnponent method.

For instance, we can write:

main.js

import { createApp } from "vue";
import App from "./App.vue";
import HelloWorld from "./components/HelloWorld.vue";

const app = createApp(App);
app.component("hello-world", HelloWorld);
app.mount("#app");

App.vue

<template>
  <div>
    <hello-world />
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

components/HelloWorld.vue

<template>
  <div class="hello">hello world</div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
};
</script>

In main.js , we import the HelloWorld component and pass that into the app.component method.

The first argument is the component name.

The 2nd argument is the component itself.

Then in App.vue , we use the component by adding the tag with the given component name.

Then in HelloWorld.vue , we add some content to the template.

The component name only works when we use the kebab-case for the tag name since we defined it with a kebab-case tag name.

We can use the hello-world component in any other component since we registered it globally.

Conclusion

We can register a global component easily with Vue 3’s app.component method.

Categories
Vue 3

Add Infinite Scrolling to a Vue.js 3 App with the Intersection Observer API

Infinite scrolling is something that we’ve to add often into our Vue 3 app.

In this article, we’ll look at how to add infinite scrolling to a Vue 3 app with the Intersection Observer API.

Add Infinite Scrolling with the Intersection Observer API

The Intersection API lets us add infinite scrolling easily in our Vue 3 app.

To do this, we assign a ref to the last element and watch when that element is displayed on the screen.

Then when the array changes, we reassign the ref to the last element that’s added then.

For instance, we can write:

<template>
  <div
    v-for="(a, i) of arr"
    :key="a"
    :ref="i === arr.length - 1 ? 'last' : undefined"
  >
    {{ a }}
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      page: 1,
      arr: Array(30)
        .fill()
        .map((_, i) => i),
      observer: undefined,
    };
  },
  methods: {
    async addObserver() {
      await this.$nextTick();
      const options = {
        root: document,
        rootMargin: "20px",
        threshold: 1,
      };

      const callback = (entries) => {
        if (entries[0].isIntersecting) {
          this.arr = [
            ...this.arr,
            ...Array(30)
              .fill()
              .map((_, i) => i + 30 * (this.page - 1)),
          ];
          this.page++;
        }
      };
      this.observer = new IntersectionObserver(callback, options);
      this.observer.observe(this.$refs.last);
    },
  },
  mounted() {
    this.addObserver();
  },
  watch: {
    arr: {
      deep: true,
      handler() {
        this.addObserver();
      },
    },
  },
};
</script>

We have a series of divs rendered from the arr array in the component template.

The ref prop is set by checking whether the index of the item is the same as arr.length — 1 .

If it is, then it’s the last item, so we assign a ref to that.

Otherwise, we don’t assign a ref to it.

The data method returns the page number and the arr array with the data to render.

In the methods object, we have the addObserver methgod to add the Intersection Observer and use that to watch when the element that we assigned the ref to appears on the screen.

To do this, we call $nextTick to make sure the elements are rendered.

Then we set the options for detecting intersection.

root is the element that has the items we watch for.

rootMargin is the margin to determine when the item is considered to be on the screen.

threshold is the threshold for the element to be displayed. It’s the portion of the element that’s appeared on the screen. And it’s a number between 0 and 1.

Next, we have the callback function that checks the isIntersecting property to see if the last element appeared.

If it has, then we update the arr array by putting more entries in it.

We also update the page value.

Next, we create the IntersectionObserver instance with the callback and options .

Then we call observe on it with the element that’s been assigned the ref to watch it appear on the screen.

We call this method with the component is mounted and when the arr array changes.

The mounted hook runs after the component is rendered, so we can get the element with the ref assigned and watch it appear on the screen.

Now when we scroll down, we should see more items appear.

Conclusion

We can use the Intersection Observer API easily to add infinite scrolling easily into our Vue 3 app.

Categories
Vue 3

Add Form Validation to a Vue 3 App with Vuelidate 2 — Validating Forms and Composition API

Vuelidate 2 is a popular form validation library made for the Vue 3 apps.

In this article, we’ll look at how to add form validation to our Vue 3 app with Vuelidate 2.

Validating Forms Before Submitting

We can validate forms right before we submit them.

To do this, we write:

<template>
  <form @submit.prevent="submitForm">
    <div>
      <input v-model="v$.name.$model" />
      <template v-if="v$.name.$dirty">
        <div v-for="error of v$.name.$silentErrors" :key="error.$message">
          {{ error.$message }}
        </div>
      </template>
    </div>
    <input type="submit" />
  </form>
</template>

<script>
import useVuelidate from "@vuelidate/core";
import { required } from "@vuelidate/validators";

export default {
  name: "App",
  setup() {
    return { v$: useVuelidate() };
  },
  data() {
    return {
      name: "",
    };
  },
  validations() {
    return {
      name: { required, $lazy: true },
    };
  },
  methods: {
    submitForm() {
      this.v$.$touch();
      if (this.v$.$error) return;
      alert("success");
    },
  },
};
</script>

We have a form that we wrapped around the input.

Then we add the submitForm method, which calls v$.$touch to trigger validation for each field in the form.

If there’re any errors, then this.$v.$error will be truthy and we stop running the function.

Otherwise, the form is valid, we proceed and the alert is shown.

Validation can also be done asynchronously.

For instance, we can write:

<template>
  <form [@submit](https://medium.com/r/?url=http%3A%2F%2Ftwitter.com%2Fsubmit "Twitter profile for @submit").prevent="submitForm">
    <div>
      <input v-model="v$.name.$model" />
      <template v-if="v$.name.$dirty">
        <div v-for="error of v$.name.$silentErrors" :key="error.$message">
          {{ error.$message }}
        </div>
      </template>
    </div>
    <input type="submit" />
  </form>
</template>

<script>
import useVuelidate from "@vuelidate/core";
import { required } from "@vuelidate/validators";

export default {
  name: "App",
  setup() {
    return { v$: useVuelidate() };
  },
  data() {
    return {
      name: "",
    };
  },
  validations() {
    return {
      name: { required, $lazy: true },
    };
  },
  methods: {
    async submitForm() {
      const isFormValid = await this.v$.$validate();
      if (!isFormValid) return;
      alert("success");
    },
  },
};
</script>

We call this.v$.$validate() to validate the form.

It returns a promise that resolves to true if all the form values are valid.

Otherwise, the returned promise resolves to false .

Composition API

We can use Vuelidate 2 with Vue 3’s Composition API./

To do this, we write:

<template>
  <div>
    <input v-model="v$.name.$model" />
    <template v-if="v$.name.$dirty">
      <div v-for="error of v$.name.$silentErrors" :key="error.$message">
        {{ error.$message }}
      </div>
    </template>
  </div>
</template>

<script>
import useVuelidate from "@vuelidate/core";
import { required, minLength } from "@vuelidate/validators";
import { computed, ref } from "vue";

export default {
  name: "App",
  setup() {
    const name = ref("");
    const requiredNameLength = ref(2);
    const rules = computed(() => ({
      name: {
        required,
        minLength: minLength(requiredNameLength.value),
      },
    }));

    const v$ = useVuelidate(rules, { name });
    return { name, requiredNameLength, v$ };
  },
};
</script>

We define the name reactive property with the ref function.

requiredNameLength is another reactive property that we use in the minLength function.

minLength validates that the inputted string is at least a given length.

We pass all the rules provided by Vuelidate into the object we return in the callback we pass into the computed property.

Then we call useVuelidate to return the object we use in the template.

Now we can display the errors and bind to the model the same way the previous example in the template.

Conclusion

We can validate our forms before submission and use Vuelidate with Vue 3’s Composition API.