How can I use Google Maps functions in Vuex's createStore()? - javascript

I am trying to set up my Vuex store states to contain an array of Marker objects from Google Maps Javascript API. Currently, my createStore function looks like this:
import { createStore } from "vuex";
export default createStore({
state: {
GCPMarkers: {
basemapMarkers: [new google.maps.Marker()],
mosaicMarkers: [new google.maps.Marker()],
},
},
// other properties
});
The Google Maps Javascript API is loaded in a Vue component named Map in my Home page:
// In Map.vue
mounted() {
const loader = new Loader({
apiKey: "my key",
version: "weekly",
});
loader
.load()
.then(this.initMap)
.catch((e) => console.log(e));
},
And my main.ts is like so:
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
createApp(App).use(store).use(router).mount("#app");
When I run my app, it reports the following in the basemapMarkers: [new google.maps.Marker()], line:
Uncaught ReferenceError: google is not defined
I understand that the error is due to google not being loaded yet by the time that import store from "./store" is ran. However, I'm not sure what the right approach to solve this problem is.
The best I could come up with is moving the loader code to my main.ts so that only when the promise from loader is resolved do I start importing other files. However, that feels clunky to me and is probably not the way.

Since you need google to be defined (loaded by Loader), you should defer the app creation until after Loader has completed. Assuming the load() succeeds in assigning the google global, you could create the app in that Promise's callback:
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
const loader = new Loader({
apiKey: "my key",
version: "weekly",
});
loader
.load()
.then(() => {
const store = require('./store').default
createApp(App)
.use(store) // ok to use store now that `google` is loaded
.mount('#app');
})
.catch((e) => console.log(e));

Related

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

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.

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

Loading an external script before loading components - Vue.js

In my Vue project, I would like to load a script from a server (e. g. https://myurl.com/API.js).
The script contains a variable, which I would like to use in my Vue component (view).
The problem is that when I load that scrip using the loadScript module:
import Vue from 'vue'
import LoadScript from 'vue-plugin-load-script';
Vue.use(LoadScript);
Vue.loadScript('https://quvia.cz:4443/portalAPI.js')
It is then loaded after the Vue component, so when try to console.log(externalScriptVariable), it is undefined. If I would setTimeout for 1 second, it would output the variable just fine.
What can I do in Vue.js to "await" the script loading, so it would load before every other Vue component?
You can use async/await
import Vue from 'vue'
import LoadScript from 'vue-plugin-load-script';
Vue.use(LoadScript);
(async function() {
await Vue.loadScript('https://quvia.cz:4443/portalAPI.js');
// other things after script loaded
})();
Or promise's then
import Vue from 'vue'
import LoadScript from 'vue-plugin-load-script';
Vue.use(LoadScript);
Vue.loadScript('https://quvia.cz:4443/portalAPI.js').then(() => {
// other things after script loaded
})
.catch(() => {
// error
});
In my case, the problems were resolved by the "window" scope. Also, if you need to access any Vue element inside the "onload" function, you need a new variable for the "this" instance.
<script>
import { mapActions } from "vuex";
export default {
name: "Payment",
methods: {
...mapActions(["aVueAction"])
},
created() {
let paywayScript = document.createElement("script");
let self = this;
paywayScript.onload = () => {
// call to Vuex action.
self.aVueAction();
// call to script function
window.payway.aScriptFunction();
};
// paywayScript.async = true;
paywayScript.setAttribute(
"src",
"https://api.payway.com.au/rest/v1/payway.js"
);
document.body.appendChild(paywayScript);
}
};
</script>
I worked with this on Vue 2.6.
What you could do is use the beforeCreate() lifecycle that vue provides and load the script from there.
import LoadScript from 'vue-plugin-load-script';
export default {
name: "App",
beforeCreate() {
LoadScript('https://quvia.cz:4443/portalAPI.js')
}
};
there are also other lifecycles that might suit your needs which you can find here: https://v2.vuejs.org/v2/guide/instance.html
Also, calling the LoadScript this in the main.js would make sure it is done before any components load

global definition does not work in vue.js

I have tried to import and define a library globally as below, but somehow it does not recognize the global variable.
in main.js,
import Vue from 'vue'
import App from './App'
import router from './router'
import VueJwtDecode from 'vue-jwt-decode'
Vue.config.productionTip = false
Vue.use(VueJwtDecode)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
in Signup.vue,
...
const payload = VueJwtDecode.decode(res.jwt);
...
and the error shows that VueJwtDecode is not defined.
If you are trying to use the named reference of VueJwtDecode, you need to reimport the library in your Signup.vue compoenent since Signup.vue doesn't understand what VueJwtDecode means.
import VueJwtDecode from 'vue-jwt-decode'
const payload = VueJwtDecode.decode(res.jwt);
However, since you have globally installed the library, it has been installed to the Vue instance, meaning that it is available from the this context within your component. As a result, you can also access it from the component context without reimporting:
const payload = this.$jwtDec(res.jwt);
As document, in your component, you need to use
this.$jwtDec(res.jwt)
instead of
VueJwtDecode.decode(res.jwt);

Vuex store modules not loading in right order when importing store directly

I'm probably not seeing obvious, but after hours I don't get it still.
The problem: when I import my Vuex store into a non-component file (a api service), one module gets loaded properly while the other one only by name, but is otherwise empty.
// store.js
import * as collections from "./modules/collections";
import * as auth from "./modules/auth";
export default new Vuex.Store({
modules: {
auth,
collections
}
});
Both these modules are near-identical. Both have
export const getters = {}
export const actions = {}
export const mutations = {}
export const state = {}
Now when in some other non-component file I DON'T include the store, my vue store looks like this:
{"auth":{"authenticated":false,"authToken":"123","loginPending":false,"loginError":{}},"collections":{"collectionsPending":false,"collectionsError":null,"collections":[]}}
Now when I import the store to use it in my service like so:
import store from '../store'
// NO OTHER MENTIONS OF STORE, ONLY IMPORTING
Suddenly only my auth module is "empty"
{"auth":{},"collections":{"collectionsPending":false,"collectionsError":null,"collections":[]}}
It has something to do with module loading.
Order of loading by adding console.log statements:
Without the import:
INIT AUTH
INIT collections
INIT store
With the import:
INIT collections
INIT store
IMPORT STATEMENT -- INTO SERVICE
INIT AUTH
I'm using the Vue Webpack Boilerplate
Sigh, sorry. Circular dependency indeed. Was expecting a warning if I'd did that, but didn't.
Thanks Emile Bergeron.

Categories