Vue 3 - show loader until all child components are loaded - vue.js

I'm having trouble displaying Vue Loader Component while waiting for other components to load.
<div id="app">
<loader v-if="!componentsReady"></loader>
<some-page v-else></some-page>
</div>
<Loader> isn't displayed since it itself is a vue component.(based on logic it's waiting for all child components to load)
app.js
Here I want to display Loader component, and wait until SomePage component and it's child components are loaded.
On mounted I'm setting that components are ready, but this way I also won't get Loader component displayed, event it's not async.
import Loader from "./Loader";
const app = createApp({
data() {
return {
componentsReady: false
}
},
components: {
SomePage: defineAsyncComponent(() => import('./somePage')),
Loader
},
mounted() {
this.$nextTick(() => {
this.componentsReady = true;
});
}
});
app.mount('#app');

In vue3 you have a Suspense component that might help cf. https://v3-migration.vuejs.org/breaking-changes/suspense.html
But I think that what you are looking for is more like the v-cloak directive cf. https://v3.vuejs.org/api/directives.html#v-cloak

Faced the same problem and think we need opposite of v-cloak.
<div v-if="false">Loading Vue....</div>
I know this is late but adding for people who may need a solution like me.
Got it from this thread.

Related

Vue3: Is it possible to register single file components using app.component(...)?

Diving into vue 3, trying to add Vue to an existing asp.net core project. Since the frontend is mostly razor pages, the app isn't being mounted with a templated component that has a hierarchy of components.
const vueApp = createApp({});
What I'm trying to do:
vueApp.component('MyComponent', require('./components/MyComponent').default);
vueApp.mount('#app');
I've also tried it this way, as described in the docs:
import { createApp } from 'vue/dist/vue.esm-browser'
import MyComponent from './components/MyComponent.vue'
const vueApp = createApp({
components: {
MyComponent
}
});
vueApp.mount('#app');
I've tried every version of this. requiring MyComponent.vue, with and without the default, importing MyComponent and using it that way (instead of require), none of them work. I just continue getting [Vue warn]: Failed to resolve component 'mycomponent' (Yes I did check the html coming back from the server, It's properly capitalized...not sure why the error is lower case).
MyComponent.vue looks like this:
<template>
<lots of vanilla html>
</template>
<script>
export default {
name: 'MyComponent',
data() {
return { some: "data" }
},
methods: { ... },
mounted() { ...}
}
</script>
//no component styling
Am I missing something here? Or is this no longer possible? I'm using the default vue-cli webpack config, if that matters.
Thanks
So, after rereading the docs (for what feels like the 10th time), I think I figured out the problem. It's actually not a Vue issue at all, it's my use of the Vue component.
In my asp.net core cshtml, I was referencing the component in PascalCase, like this:
<MyComponent />
Turns out this is a no no. By convention (enforced by the browser I guess), custom elements can only be referenced in the DOM using kebab-case, like this:
<my-component />
My vue app is still defining the component in PascalCase.
My main.js file is importing MyComponent, then passing it into the createApp options.components object.
const vueApp = createApp({
components: {
MyComponent
}
});
The more you know, I guess.

Lazy loading a specific component in Vue.js

I just make it quick:
In normal loading of a component (for example "Picker" component from emoji-mart-vue package) this syntax should be used:
import {Picker} from "./emoji-mart-vue";
Vue.component("picker", Picker);
And it works just fine.
But when I try to lazy load this component I'm not sure exactly what code to write. Note that the following syntax which is written in the documentation doesn't work in this case as expected:
let Picker = ()=>import("./emoji-mart-vue");
The problem, I'm assuming, is that you're using
let Picker = ()=>import("./emoji-mart-vue");
Vue.component("picker", Picker);
to be clear, you're defining the component directly before the promise is resolved, so the component is assigned a promise, rather than a resolved component.
The solution is not clear and depends on "what are you trying to accomplish"
One possible solution:
import("./emoji-mart-vue")
.then(Picker=> {
Vue.component("picker", Picker);
// other vue stuff
});
This will (block) wait until the component is loaded before loading rest of the application. IMHO, this defeats the purpose of code-spliting, since the application overall load time is likely worse.
Another option
is to load it on the component that needs it.
so you could put this into the .vue sfc that uses it:
export default {
components: {
Picker: () => import("./emoji-mart-vue")
}
};
But this would make it so that all components that use it need to have this added, however, this may have benefits in code-splitting, since it will load only when needed the 1st time, so if user lands on a route that doesn't require it, the load time will be faster.
A witty way to solve it
can be done by using a placeholder component while the other one loads
const Picker= () => ({
component: import("./emoji-mart-vue"),
loading: SomeLoadingComponent
});
Vue.component("picker", Picker);
or if you don't want to load another component (SomeLoadingComponent), you can pass a template like this
const Picker= () => ({
component: import("./emoji-mart-vue"),
loading: {template:`<h1>LOADING</h1>`},
});
Vue.component("picker", Picker);
In PluginPicker.vue you do this:
<template>
<picker />
</template>
<script>
import { Picker } from "./emoji-mart-vue";
export default {
components: { Picker }
}
</script>
And in comp where you like to lazy load do this:
The component will not be loaded until it is required in the DOM, which is as soon as the v-if value changes to true.
<template>
<div>
<plugin-picker v-if="compLoaded" />
</div>
</template>
<script>
const PluginPicker = () => import('./PluginPicker.vue')
export default {
data() = { return { compLoaded: false }}
components: { PluginPicker }
}
// Another syntax
export default {
components: {
PluginPicker: () => import('./PluginPicker.vue')
}
}
</script>

VUE add a component with JS

Can I create/mount a VUE component calling a JS function in a fullyloaded page?
As a async call? Something like:
function getComponent(obj){
return <component parameters="obj"></component >;
}
Can I create/mount a VUE component calling a JS function in a fullyloaded page?
I think you want to load/mount the component in a certain condition and not on page initialization.
If that the case then you can take advantage of lazy-loading and dynamic components:
<template>
//...
<button #click="activateComponent">Activate component</button>
<component :is="dynamicComponent" />
//...
</template>
<script>
export default {
components: {
MyCmp: () => import('./MyCmp.vue') //lazy loading
},
data: () => ({
dynamicComponent: null
}),
methods: {
activateComponent () {
this.dynamicComponent = 'MyCmp'
}
}
}
</script>
I think what you are looking for is Vue dynamic components. What that allows you is to choose which component to load after the original component has loaded. You can do that whenever and however you want to, aka in an async way. You can read more in the Vue guides. All the code you need is there so won't repost it here again.

Pass information from a child component to a parent component with Vue.js

Thanks for reading my question.
I have doubts about how to accomplish this in my Vue.js app.
Parent Component: App.vue
Child Components: Loading.vue, ProductsList.vue, NoProductList.vue
The App component is the container. When the page loads, the Loading component is displayed.
The Loading component then checks for products. If there are products the Loading component will return the products as an object.
If there aren't any products the Loading component will return null.
How do I tell the App component which component to use based on the Loading components return value? ie.. Load the ProductsList component if there are products and the NoProduct component when there aren't any products?.
What's the correct way to do notify the parent component about which component it should use in Vue?
Should I use $emit (pass data from child to parent), State Management https://v2.vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch or a Bus component?
Some code...
Loading.vue component (child):
export default {
name: 'loading',
data () {
return {
message: ''
}
},
mounted: function () {
this.loadingProcess();
},
methods: {
loadingProcess: function () {
//process
this.$emit('found-products', 'hola');
}
}
App.vue component (parent):
<template>
<div>
<div id="resultComponent" #found-products="checkProductsList">
<loading></loading>
...
</div>
</div>
</template>
Thanks in advance.
For parent-child interactions, the correct way is for the child to $emit and the parent to set props.
State management and buses are for more complex systems where data must be shared by not-closely-related components.

How to set keyup on whole page in Vue.js

Is it possible to set a v-on:keyup.enter on the whole page, not only for an input element in javascript framework Vue.js ?
Perhaps a better way to do this is with a Vue component. This would allow you to control when you listen to events by including or not including the component. Then you could attach event listeners to Nuxt using the no-ssr component.
Here is how you create the component:
<template>
<div></div>
</template>
<script>
export default {
created() {
const component = this;
this.handler = function (e) {
component.$emit('keyup', e);
}
window.addEventListener('keyup', this.handler);
},
beforeDestroy() {
window.removeEventListener('keyup', this.handler);
}
}
</script>
<style lang="stylus" scoped>
div {
display: none;
}
</style>
Then on the page you want to use that component you'd add this HTML:
<keyboard-events v-on:keyup="keyboardEvent"></keyboard-events>
And then you'll have to add your event handler method:
methods: {
keyboardEvent (e) {
if (e.which === 13) {
// run your code
}
}
}
Short answer is yes, but how depends on your context. If you are using vue-router as I am on a current project, you would want to add to the outer-most element you want that applied to. In my case I'm using the actual app.vue entry point's initial div element.
There is one catch that I believe is a hard requirement, the element has to be within the potentially focusable elements. The way I'm dealing with that is setting a -1 tabindex and just declaring my super-hotkeys (mostly for debug purposes right now) on the parent element in my app.
<template>
<div
id="app-main"
tabindex="-1"
#keyup.enter="doSomething"
>
<everything-else></everything-else>
</div>
</template>
EDIT:
As a side note, I also added a touch of additional configuration to my vue-router to make sure the right element is focused when I transition pages. This allows the pageup/pagedown scrolling to already be in the right section based on the content area being the only scrollable section. You'd also have to add the tabindex="-1" to the app-content element as well.
router.afterEach(function (transition) {
document.getElementById('app-content').focus();
});
and the basis of my app-content component:
<template>
<div id="app-content" tabindex="-1">
<router-view
id="app-view"
transition="app-view__transition"
transition-mode="out-in"
></router-view>
</div>
</template>
I created a small npm module that takes care of global keypress events in Vue, hope it makes someone's life easier:
https://github.com/lupas/vue-keypress
My simplest approach:
Add into your root Vue component (or any other component):
new Vue({
//...
created() {
window.addEventListener('keypress', this.onKeyPress);
},
beforeDestroy() {
window.removeEventListener('keypress', this.onKeyPress);
},
methods: {
onKeyPress(e) {
console.log('KEYPRESS EVENT', e)
//... your code
}
}
//...
})
In Vue 3 composition API, you can do it with a composable:
import { onMounted, onUnmounted } from "vue";
export function useKeyupEvent(handler) {
onMounted(() => document.addEventListener("keyup", handler));
onUnmounted(() => document.removeEventListener("keyup", handler));
}
and then in your component setup:
useKeyupEvent( event => console.log(event))