Tips for showcasing a "loading" animation as a lazy-loaded route component loads

Utilizing webpack's code splitting feature, I have divided my application into multiple chunks to prevent the entire bundle from being downloaded at once when a user visits my website.

Some routes require large chunks that may take some time to download. Although this is acceptable, it can be confusing for users as they are unaware of the page loading progress when clicking on internal links. Therefore, I need to find a way to display a loading animation or indicator.

This is how my router is set up:

[
  {
    path: '/',
    component: () => import(/* webpackChunkName: 'landing' */ './landing.vue'),
  },
  {
    path: '/foo',
    component: () => import(/* webpackChunkName: 'main' */ './foo.vue'),
  },
  {
    path: '/bar',
    component: () => import(/* webpackChunkName: 'main' */ './bar.vue'),
  },
]

The Vue.js guide on Advanced Async Components demonstrates how to show a "loading" component while resolving the main component – exactly what I need. However, it also mentions:

Note that when used in vue-router, these properties are ignored since async components are resolved before route navigation occurs.

How can I implement this functionality in vue-router? If not achievable, lazily-loaded components would provide little benefit and result in a subpar user experience.

Answer №1

If you're looking to implement a loading state using navigation guards, here's how you can do it:

For example, if you want to utilize something like "nprogress," you can follow this approach:

http://jsfiddle.net/xgrjzsup/2669/

const router = new VueRouter({
  routes
})

router.beforeEach((to, from, next) => {
  NProgress.start()
  next()
})
router.afterEach(() => {
  NProgress.done()
})

Alternatively, if you prefer to display something in-place:

http://jsfiddle.net/h4x8ebye/1/

Vue.component('loading',{ template: '<div>Loading!</div>'})

const router = new VueRouter({
  routes
})

const app = new Vue({
  data: { loading: false },
  router
}).$mount('#app')

router.beforeEach((to, from, next) => {
  app.loading = true
    next()
})

router.afterEach(() => {
  setTimeout(() => app.loading = false, 1500)
})

You can then include the following in your template:

<loading v-if="$root.loading"></loading>
  <router-view v-else></router-view>

This functionality can also be easily encapsulated into a small component instead of relying on the $root component for managing the loading state.

Answer №2

In my personal experience, here is how I resolved a similar situation:

I implemented a global "loading" state in my application using Vuex, allowing any component to access it. However, you can customize this feature using any method that suits your needs.

The simplified solution operates as follows:

function componentLoader(store, fn) {
  return () => {
    // (Vuex) Initiates loading
    store.commit('LOADING_BAR_TASK_BEGIN');

    // (Vuex) Fires when loading is complete
    const done = () => store.commit('LOADING_BAR_TASK_END');

    const promise = fn();
    promise.then(done, done);
    return promise;
  };
}

function createRoutes(store) {
  const load = fn => componentLoader(store, fn);

  return [
    {
      path: '/foo',
      component: load(() => import('./components/foo.vue')),
    },
    {
      path: '/bar',
      component: load(() => import('./components/bar.vue')),
    },
  ];
}

By wrapping each () => import() with the load() function I created, I am able to efficiently manage the loading state. This approach directly monitors the promise instead of relying on router-specific hooks for loading determinations.

Answer №3

If you're working on a Vue.js project and need to implement global routing, you can utilize the following code snippet:

const router = new Router({
  routes: [
      { path: '/', name: 'home', component: Home },
      { path: '/about', name: 'about', component: About }
  ]
})

router.beforeResolve((to, from, next) => {
  // Check if it's not the initial page load.
  if (to.name) {
      // Display the route progress bar.
      NProgress.start()
  }
  next()
})

router.afterEach((to, from) => {
  // Complete the animation of the route progress bar.
  NProgress.done()
})

Answer №4

If you want to display a loading indicator in one place for all pages, including nested ones, the accepted answer provides an excellent solution.

However, if you're searching for a way to wait for RouterView to finish with lazy loading components and show a loading indicator, here's some code you can use:

<script setup lang="ts">
import Spinner from '@lib/components/spinner/Spinner.vue';
import { routerViewWithSuspenseService } from './router-view-with-suspense';

routerViewWithSuspenseService.register();
const { isCurrentIndexLoading } = routerViewWithSuspenseService.use();
</script>

<template>
    <template v-if="isCurrentIndexLoading">
        <slot>
            <div class="grid size-full place-items-center">
                <Spinner class="size-10"></Spinner>
            </div>
        </slot>
    </template>
    <RouterView v-else></RouterView>
</template>
import { useTimeoutFn } from '@vueuse/core';
import { computed, shallowRef } from 'vue';
import { onBeforeRouteLeave, useRouter } from 'vue-router';

function createRouterViewWithSuspenseService() {
    const registered = shallowRef(false);
    const index = shallowRef(0);
    const isLoading = shallowRef(false);
    const { start, stop } = useTimeoutFn(() => (isLoading.value = true), 100, { immediate: false });

    function register() {
        if (registered.value) {
            return;
        }

        registered.value = true;

        const router = useRouter();

        router.beforeEach(() => {
            start();
        });

        router.afterEach(() => {
            stop();
            isLoading.value = false;
        });
    }

    function use() {
        index.value += 1;
        const currentIndex = index.value;

        onBeforeRouteLeave(() => {
            index.value -= 1;
        });

        return {
            isCurrentIndexLoading: computed(() => currentIndex === index.value && isLoading.value),
        };
    }

    return {
        register,
        use,
    };
}

export const routerViewWithSuspenseService = createRouterViewWithSuspenseService();

I've substituted all RouterView code with RouterViewWithSuspense, allowing the loading indicator to be displayed.

Hopefully, this saves someone a lot of headaches! :D

P.S. I'm still uncertain why the native Suspense doesn't work effectively with dynamic components used alongside vue-router.

Answer №5

The answer provided includes JSFiddle links that are not functional due to the use of @latest (which loads Vue 3) instead of Vue 2's API. I have corrected these links:

A key difference is the router definition now needs a history option as Vue-Router 3 automatically selected the most suitable one, whereas Vue-Router 4 requires explicit declaration. Additionally, I have included a scroll behavior function as it is not clearly documented that a Promise/Resolve is needed when using lazy loading, which may be why many users visit this page. The createRouter API is a new feature in Vue-Router 4 to align with the changes in the new Vue 3 createApp API.

const router = VueRouter.createRouter({
  history: VueRouter.createWebHashHistory(),
  routes,
  scrollBehavior: function () {
    return new Promise((resolve) => {
      resolve({ left: 0, top: 0 });
    });
  }
});

Apart from these changes, the mounting process remains consistent with the new API.

const app = Vue.createApp();
app.use(router);
app.mount('#app');

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Updating Angular 8 Component and invoking ngOninit

Within my main component, I have 2 nested components. Each of these components contain forms with input fields and span elements. Users can edit the form by clicking on an edit button, or cancel the editing process using a cancel button. However, I need to ...

PHP and Bootstrap combine in this dynamic carousel featuring thumbnail navigation

Looking to create a dynamic bootstrap carousel with image thumbnails at the bottom? After exploring various code snippets and solutions, I stumbled upon this link. While the code worked, I found the thumbnails to be too small. Below are the functions I use ...

JavaScript Plugins for Cordova

The more I delve into the inner workings of Cordova, the clearer it becomes to me. Yet, one area that continues to perplex me is the structure of JavaScript plugins. Typically, I write my JavaScript code as follows, adhering to what I believe is the stand ...

Encountered an error while trying to access an undefined property during an Angular Karma

I'm currently working on testing a service function that involves multiple $http.get() calls. The function being tested returns a promise but the test is failing with an error stating response is undefined. Below is the test script: it('should ...

Guide to fetching and displaying a complex array API using JavaScript's fetch method

Recently, I've been delving into the world of APIs and making sure I call them correctly. Using the fetch method has been my go-to so far, and here's a snippet of the code where I reference an API: fetch('https://datausa.io/api/data?d ...

How can I rename a parameter while uploading a file with Vue2-Dropzone?

While using Vue2-Dropzone to upload files, the default parameter name is set to "file". However, I would like to customize it and change it to "upload". I attempted to modify this by utilizing the vdropzone-sending method. Unfortunately, this resulted in ...

Recommendation: 3 options for radio buttons on the registration form

My form includes a section where users need to choose if they want to sign up for a session that occurs 3 times daily. The catch is, only 5 applicants can enroll in each session (AM, Mid-day, PM). It's a competition to secure a spot. Here is the form ...

How can I make my navbar stay fixed in place and also activate the transform scale functionality?

After fixing the position of my navbar with the class "top," I noticed that the transform property scale does not work on the div element I applied. The hover effect on the box only works when I remove the position from the navbar. This is the HTML code: ...

Phonegap - Retaining text data in a checklist app beyond app shutdown

This is my first time developing an app with Phonegap. I am looking to create a checklist feature where users can input items into an input field. However, I am struggling with figuring out how to save these items so that they remain in the checklist even ...

Tips on how to update the styling of an active link

http://jsfiddle.net/G8djC/2/ Looking to create a tabbed area where content changes based on the tab clicked. The Javascript function switches the link class to active upon clicking. However, struggling to change the color of the active tab beyond the firs ...

I am searching for an uncomplicated approach to eliminate duplicate arrays of objects

Question: const info=[ { id: 123, name: "dave", age: 23 , address:city:"chennai"}, { id: 456, name: "chris", age: 23, address:city:"delhi"}, { id: 789, name: "bob", age: 23, address:city:& ...

Creating a messaging platform

I'm trying to figure out the best approach for developing a chat application that can handle thousands of users. I'm unsure about how to efficiently ping the server using AJAX at intervals of every second or less, and then check for any new reco ...

While attempting to deploy my project on Vercel by pulling in my code from GitHub, I ran into this error

As a self-taught Front End developer diving into building and hosting my first web page using React, I've encountered this warning. Any suggestions on how to resolve it? Cloning gitohub.com/Passion94/React-Apps (Branch: main, Commit: da89a2a) Cloning ...

Node.js Multer encountering undefined req.file issue when handling multiple file uploads

FIXED: ( NO req.file ) ( YES req.files ) My project requires the ability to upload multiple files. If single image uploads are working but multiple image uploads aren't (uploading to files), I need req.file.filename in order to write the image path ...

What could be causing the lack of updates to my component in this todo list?

Based on my understanding, invoking an action in MobX should trigger a rerender for the observer. However, when I call the handleSubmit method in my AddTask component, it doesn't cause the TaskList observer to rerender. Should I also wrap AddTask in a ...

Enhance your loader functionality by dynamically updating ng-repeat using ng-if

To better illustrate my issue, here is a link to the fiddle I created: https://jsfiddle.net/860Ltbva/5/ The goal is to show a loading message while the ng-repeat loop is still loading and hide it once all elements have been loaded. I referenced this help ...

The babel-preset-es2016 plugin is in need of the babel-runtime peer dependency, however it seems that it

While I am aware that npm no longer automatically installs peer dependencies, why do I still receive a warning after manually installing them? ➜ npm install babel-runtime -g /usr/local/lib └─┬ <a href="/cdn-cgi/l/email-protect ...

Stop jQuery Tab from Initiating Transition Effect on Current Tab

Currently, I am utilizing jQuery tabs that have a slide effect whenever you click on them. My query is: How can one prevent the slide effect from occurring on the active tab if it is clicked again? Below is the snippet of my jQUery code: $(document).read ...

Issue encountered when attempting to generate a mongoose schema using a JSON document

Can I define this JSON file structure as a mongoose schema? Mongoose lacks an object type, and I'm unsure how to proceed with it. { "Moutainbike": { "Cross": { "size": [ "395 mm", "440 mm", "480 mm", "535 mm" ], "color" ...

Adding a command to open a new browser tab using JavaScript code can easily be accomplished by following these steps. By using Terminal, you can quickly and efficiently implement this feature

I'm new to coding and trying to create a terminal simulation. You can check out my code here: https://codepen.io/isdampe/pen/YpgOYr. Command: var coreCmds = { "clear": clear }; Answer (to clear the screen): function clear(argv, argc ...