How do I access Vue instance inside a js file in Vue3? - javascript

In Vue2, I was able to access my Vue instance to make use of components registered with Vue.
test.js
import Vue from 'vue'
export function renderLogin () {
Vue.toasted.show('Please login again', { type: 'error', duration: 2000 })
}
In the above code, I am able to access the toasted package as I have already registered it with Vue in my main.js. However, in Vue3 I'm unable to use the toasted package as I'm unable to access the Vue instance inside a js file.
Need help on how to access Vue instance('this') inside a js file.

After a day of searching, I was able to access the toasted component from the vue instance inside a js file.
First, we would have to export the app instance to be able to read it in a js file
main.js
export const app = createApp({
render() {
return h(AppWrapper);
},
});
Next, we would have to register our component in our globalProperties of our app's instance.
app.config.globalProperties.$toast = toast;
We can now import the app instance in our js file and access toast component
test.js
import { app } from '#/main.js'
app.config.globalProperties.$toast('Toast working fine', {
type: 'success',
duration: 2000,
})
Hope this helps someone out. Please let me know if there are other/better ways. Thank you

// Vue 3 Composition API
<script>
import { getCurrentInstance } from 'vue';
export default {
setup() {
const _instance = getCurrentInstance();
const vueInstance = _instance.appContext;
},
};
</script>
It's not exactly the way as in Vue2, but this will probably expose what you are looking for.
If you want to make a package globally available in Vue3 you probably need to add the following code to a plugin:
//* This will help for accessing the toasted instance in other files (plugins)
app.config.globalProperties.$toasted = toasted;
//* This will expose the toasted instance in components with this.$toasted
app.provide('$toasted', toasted);
With this you are able to get the toasted instance in the options api with: this.$toasted
And with the composition api:
const { $toasted } = _instance.appContext.app.config.globalProperties;
And in another plugin with:
constructor(app) { app.config.globalProperties; }

You can use provider/inject.
For example if you want to use axios across my components, provide axios in your main.js
import { createApp } from "vue";
import App from "./App.vue";
import axios from "axios";
const app = createApp(App);
app.provide("http", axios);
app.mount("#app");
Then in SFC component you could access by 2 ways:
// Composition API
<script>
import { inject } from 'vue'
export default {
setup() {
const http = inject("http");
http.get("https://jsonplaceholder.typicode.com/todos/1").then((response) => {
console.log(response.data);
});
}
}
</script>
// Vue 2 options API
<script>
export default {
inject: ["http"],
}
</script>
Original answer here.

Related

VueJS 3 CLI project, TypeError default is not a constructor from ModalService

After creating a CLI project, I need to add a Modal dialog, after a bit of searching I found what seems to be a good example at Create Reusable MODALS Using VUE JS, by Shmoji . The tutorial is well written and there is a YouTube as well.
In the modual.service.js file the following code exists:
import Vue from 'vue';
export default new Vue({
methods: {
open(component, props = {}) {
return new Promise((resolve, reject) => {
this.$emit('open', { component, props, resolve, reject });
});
}
}
})
Here is where I get the error that reads:
Uncaught TypeError: vue__WEBPACK_IMPORTED_MODULE_1__.default is not a constructor
It is pointing to the second line of code, export default new Vue({
In my main.js file, I already create Vue so it does not make sense to me why the modal author is trying to overwrite the Vue that already exists?
//main.js
import { createApp, reactive, computed, VueElement } from 'vue'
import App from './App.vue'
import router from './router'
import './assets/styles.css'
import moment from 'moment'
const app = createApp(App).use(router)
app.config.globalProperties.$moment=moment;
app.mount('#app')
Seems to me the global open method needs to be included in main.js but not sure how that is done or how to get around the above error if the modal service open method stays in its own file?
This Modal Dialog is not compatible with Vue3. I answered this question in more detail in this StackOverflow posting.

How to import Library into Vue3 project

could someone help me import a library to my vue3 project so that I can use it in all components?...
I'am trying to import 'moments.js' to my project
Its installed with npm
in my 'main.js' (entry) I import it like:
import { createApp } from "vue"
import App from "./App.vue"
import moment from "moment"
const app = createApp(App)
app.use (moment)
app.mount("#app")
but when I try to console.log(this.moment) from another component I get errors that this.moment is not a function
You can bind moment as a global property on the Vue instance by during the created lifecycle hook in the like manner.
const { createApp } = require('vue');
import App from "./App.vue";
import moment from 'moment';
const MomentPlugin = function (Vue, options) {
Vue.mixin({
created: function () {
this.moment = moment
}
})
}
const app = createApp(App)
app.use(MomentPlugin).mount("#app");
moment function is then available in template context or anywhere the Vue instance is available in scope.
For anyone stumbling onto this post. I changed the code to:
import { createApp } from "vue"
import App from "./App.vue"
import moment from "moment"
const app = createApp(App)
app.provide("moment", moment)
app.mount("#app")
inside other components:
export default {
inject: ["moment"],
// Other code can now use "moment"
}
I would try using this package
https://www.npmjs.com/package/vue-moment
as it is vue-specific. It is a wrapper for moment.
Check the Readme file also for instructions.
https://github.com/brockpetrie/vue-moment
import Vue from 'vue'
import VueMoment from "vue-moment"
Vue.use(VueMoment));
Your case
import { createApp } from "vue"
import App from "./App.vue"
import VueMoment from "vue-moment"
const app = createApp(App)
app.use (VueMoment)
app.mount("#app")
You can use moment like this in any component.
methods: {
moment: function () {
return moment();
}
},
app.use() is for adding Vue plugins to the app. It should be possible to convert Moment.js to a plugin - see "Writing a plugin" in the documentation but it shouldn't be necessary.
You can just import moment.js in any component where you want to use it and the bundling process will make sure that the code is not duplicated anywhere.

How to mount quasar app to dom after firebase connection is initialized?

Hi how do I initialize my quasar app once after I have established a connection to firebase. I need to do this because my route guards checks whether a user is logged in, but if a user refreshes this causes firebase to check whether a current user exists but since it's a async operation it initially returns null. I have previously achieved this using vue where I have done this in my main.js file, but I'm unsure how to do this is quasar boot file.
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './assets/main.css'
import { authService } from './firebase/config'
let app
authService.onAuthStateChanged(() => {
if(!app){
app = createApp(App).use(router).mount('#app')
}
})
Here inside main.js standard vue app, we wait for Firebase Auth to initialize and only after .onAuthStateChanged() method fires, we create our Vue App.
In an quasar app there is no main.js but instead boot files which run some code before main app starts.
Below is what a standard boot files looks like but I am unsure how to convert my main.js to a executable boot file, seems like some properties are missing like mount, createApp & use.
// import something here
// "async" is optional!
// remove it if you don't need it
export default async ({ /* app, router, store */ }) => {
// something to do
}
Ideally you create a boot file for initializing Firebase in a Quasar project.
Boot files fulfill one special purpose: they run code before the App’s Vue root component is instantiated while giving you access to certain variables, which is required if you need to initialize a library, interfere with Vue Router, inject Vue prototype or inject the root instance of the Vue app.
Create a boot file using the following command:
quasar new boot firebase
This will create a boot file /src/boot/firebase.js
Install Firebase using npm (or yarn) and then add the following code in your boot file:
import firebase from 'firebase/app';
import 'firebase/auth';
const firebaseConfig = {...};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
}
export const auth = firebase.auth();
As mentioned earlier, boot file runs before Vue component is instantiated. Now you can access this instance of Firebase Auth in any component you want.
import {auth} from "#/boot/firebase"
auth.onAuthStateChanged((user) => {
//...
})
This got it working.
export default async ({ app, router, store }) => {
return new Promise(resolve => {
const unsubscribe = authService.onAuthStateChanged(() => {
resolve()
unsubscribe()
})
})
}
Here is also a useful article on quasar boot flow https://quasar.dev/quasar-cli/boot-files#quasar-app-flow

Add all vue components to window array

I currently have a strange Vue setup due to our websites all using an old system.
What we have had to do is create an instance of Vue for each component (usually not many). What I want to do for all components is to pass their name and reference to the element into an array, just for reference when debugging issues on live issues.
app.js
import Vue from "vue";
import Axios from 'axios';
import inViewportDirective from 'vue-in-viewport-directive';
window.components = [];
Vue.component( 'video-frame', () => import('./components/VideoFrame.vue' /* webpackChunkName: "video-frame" */) );
Vue.prototype.$event = new Vue();
Vue.prototype.$http = Axios;
Array.prototype.forEach.call(document.querySelectorAll(".app"), (el, index) => new Vue({el}));
Now i'm adding the following code to each component, is there not a way I can do this once within my app.js and have all the components automatically do the following:
mounted() {
window.components.push({
tag: this.$vnode.tag,
elm: this.$vnode.elm
});
},
You can use a global mixin like this:
Vue.mixin({
mounted: function() {
window.components.push({
tag: this.$vnode.tag,
elm: this.$vnode.elm
});
}
});
That will ensure that code will run on the mounted hook on every single one of your Vue instances.
Reference: https://v2.vuejs.org/v2/guide/mixins.html

Vue + Jest global config carrying over into spec files

I'm using VueJS and Jest for unit testing my components.
I'm also using the Bootstrap Vue library for styling. I need to use this plugin in my Jest tests in order to remove some console warnings about unknown plugins.
I have created a setup file as so:
import { createLocalVue } from '#vue/test-utils'
import BootstrapVue from 'bootstrap-vue'
const localVue = createLocalVue()
localVue.use(BootstrapVue)
And configured Jest to use this before every test.
setupFiles: ['<rootDir>/tests/unit/setup']
However, to remove the warnings from the console, I need to use the localVue instance when mounting the component:
const wrapper = shallowMount(MyComponent, {
localVue,
propsData: { value: 'someVal }
})
However there is no way that I can see to get the localVue instance created in the setup.js into the test spec files.
If I do this:
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue)
It works fine, but this is bad as we should not be using the Global Vue instance in Jest tests.
Is there a way to do what I want to do, or am I going to have to construct the Bootstrap Vue plugins (and others as they come along...) into every single test file?
You could try to assign the localVue variable as a global variable in your setupFiles. This would allow you access localVue variable in every test, like so:
import { createLocalVue } from '#vue/test-utils'
import BootstrapVue from 'bootstrap-vue'
global.localVue = createLocalVue()
global.localVue.use(BootstrapVue)
Then use it like this in your test:
const localVue = global.localVue
const wrapper = shallowMount(MyComponent, {
localVue,
propsData: { value: 'someVal' }
})

Categories