How to force vue to wait for async <script> to run before mounting components - vue.js

I have an async js script which is loaded at the top of index.html in my vue project. This script exposes several functions into the window object which I would like to be able to call. I would like to be able to call these functions in the mounted() lifecycle hook, but the async function appears to complete only after mounted has finished. Is there a way I can force the vue instance to wait for all <script> to complete before mounting the root component?

According to this issue in Github https://github.com/vuejs/vue/issues/7209 it seems that the async hooks in Vue lifecycle mounted() created() etc, is only for the ability to call async functions. but the lifecycle itself is synchronous.
Here are some ideas to manage this problem:
You can wrap your component with v-if and render it just as soon as your data is ready.
If you render a component by router - you can use the lifecycle of Vue router. and call your async function before enter to Vue page. do it like this:
export default {
beforeRouteEnter (to, from, next) {
// called before the route that renders this component is confirmed.
next()
}
}
this next() function calls the first Vue lifecycle hook...
full tutorial: https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards

If anyone is interested, here is how I solved my problem:
I modified the js file that the <script> was referencing to set global.initComplete=false initially, and when the script was complete, I marked it as true.
For main.js file, I used:
import Vue from 'vue'
import App from './App.vue'
function init() {
let vm = await new Vue({
render: h => h(App),
})
if(!global.initComplete) {
setTimeout(init, 50)
} else {
vm.$mount('#app')
}
}
init()

Related

Using the mitt-library in Vue with Webpack hot-reload causes problems?

In my Vue3 app, I'm using the mitt eventbus library to emit and receive events between components.
I put this in onMounted of a list component that needs to refresh:
mitt.on("list_refresh", (evt) => {
refresh();
});
In another component that contains the list-component as a child (or grandchild), I do this in a method:
mitt.emit("list_refresh", {});
This works ok, but while developing with hot-reload on, the events seem to be emitted multiple times, as if they're created extra each time the app reloads, instead of overwriting the old ones.
When I reload the entire page in the browser, it works fine again.
Any idea to prevent this?
It looks like your component is missing a corresponding off() call to remove the event listener. During hot reload, the current component instances unmount, and new ones mount; so if you're not removing current event listeners, you'll just pile on new event listeners. To resolve the issue, use the onUnmounted hook to remove the event listener when the component is removed from the DOM.
Also, make sure to pass cached function references (instead of inline functions) to mitt.on() and mitt.off() to ensure the given event listener lookup succeeds in mitt.off():
// mitt.on('list_refresh', () => refresh()) ❌
mitt.on('list_refresh', refresh) ✅
mitt.off('list_refresh', refresh)
Your setup() should look similar to this:
import { onMounted, onUnmounted } from 'vue'
export default {
setup() {
const refresh = () => { /*...*/ }
onMounted(() => mitt.on('list_refresh', refresh))
onUnmounted(() => mitt.off('list_refresh', refresh)) 👈
}
}

What is an equivalent of `created()` in the Vue.js composition api?

Could you advise what is the equivalent of the created() in the new Vue composition API, which I'm using from within Vue2 like this:
import { reactive, toRefs } from '#vue/composition-api'
From the Composition API docs on Lifecycle Hooks:
Because setup is run around the beforeCreate and created lifecycle hooks, you do not need to explicitly define them. In other words, any code that would be written inside those hooks should be written directly in the setup function.
Anything you would have done in the created hook you can do in setup.
to help the community, the created() method can be used this way.
hope this helps :)
<script>
import TenantsAPI from "#/api/tenants";
export default {
setup() {
const tenantsAPI = new TenantsAPI(); //compositon api
};
}
//options api
//async created(){
// this.tenantsAPI = new TenantsAPI();
//}
</script>

VueJS get AJAX data before routing / mounting componenents

I want to initialize some base data once before having VueJS does any routing / mounting.
Now I found out about the Global Navigation Guard router.beforeEach. But It triggers not only on the initial load (page load), but every route that is triggered. Now I could put in some sort of if-statement to have the code run only once, but that's not my preferred way of solving this:
// pseudo:
router.beforeEach((to, from, next) => {
if (state.initialized === false) {
await store.dispatch(init)
}
// the rest of my routing guard logic....
})
I'd prefer not having the if-statement run everytime (knowing it's only going to be true once, and false forever after).
Is there an official way to have (ajax) code run only once, AFTER vue is initialized (so I have access to vuex state, etc.), BEFORE any routing has started.
You can easily perform an asynchronous task before mounting your root Vue instance.
For example
// main.js
import Vue from 'vue'
import store from 'path/to/your/store'
import router from 'path/to/your/router'
import App from './App.vue'
store.dispatch('init').then(() => {
new Vue({
store,
router,
render: h => h(App)
}).$mount('#app')
})
It would be a good idea to show something in index.html while that's loading, eg
<div id="app">
<!-- just an example, this will be replaced when Vue mounts -->
<p>Loading...</p>
</div>

How to trigger js-vue-modal within actions

I'm using the vue-js-modal library. I also use vuex. How do I trigger open a modal within actions? (as this.$modals.show('modal-name') only works within a Vue component)
The plugin adds $modals to the Vue class prototype, so all you would need to do is import Vue in your store and then emit the event to show the model. For example:
// store.js
import Vue from 'vue'
const vm = new Vue()
...
actions: {
openModal: (context, params) => {
// do things
vm.$modals.show('my-modal')
// do stuff
}
}

Access Vue.js plugin from main.js

I'm creating a plugin and I just wonder why I can't access it in main.js file. Here's how Auth.js looks like:
const Auth = {
install(Vue) {
Vue.prototype.$isGuest = function () {
console.log('This user is a guest.');
}
Vue.prototype.$getAuthToken = function () {
console.log('Auth token will be returned.');
}
}
}
export default Auth
This is main.js:
import Auth from '#/helper/Auth'
Vue.use(Auth)
However, when I execute console.log(this.$isGuest()), it doesn't work. It actually returns the following:
main.js?1c90:25 Uncaught TypeError: this.$isGuest is not a function
The problem is that this method works when I call it in components such as Dashboard.vue and things like that.
I have a way to avoid calling isGuest method within main.js (I can call it in Layout.vue), but I'm more curious why it doesn't work in main.js.
Maybe because Vue hasn't been initialized yet, but even if I put the console.log() line at the end of the file, still doesn't work.
Thanks,
N.
If you are calling this.$isGuest() outside of Vue, you will get the error you describe. That's because this is not a Vue object. What this is depends on how you are building your code, but given you are using import it's probably the module.
Also, you are adding $isGuest to the prototype of Vue. That means that the function is only going to be available on actual instances of Vue objects. That is why it is available in your components.
If you want to use it in the main script, the only place you will be able to get to it is inside the Vue object in a lifecycle handler, method, or computed. For example:
new Vue({
mounted(){
console.log(this.$isGuest()) // this should work
}
})