Vuejs3 how to register a custom global directive in a separate folder? - javascript

I wanted to move my global custom directive into a separate folder and import it from a file, but I fail to do so in Vue3.
I got a:
Uncaught TypeError: Cannot read properties of undefined (reading 'directive')
Here are my files:
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import VueGtag from 'vue-gtag'
import '#/plugins/gtag'
createApp(App)
.use(
VueGtag,
{
config: { id: process.env.VUE_APP_GOOGLE_ANALYTICS_ID },
pageTrackerTemplate(to) {
return {
page_title: to.name,
page_path: to.path,
}
},
},
router
)
.use(router)
.mount('#app')
/plugin/gtag.js
import Vue from 'vue'
import { event } from 'vue-gtag'
const track = binding => () => {
event('click', {
event_category: binding.value.category,
event_label: binding.value.label,
value: 1,
})
}
Vue.directive('track', {
beforeMount(el, binding) {
const trackFn = track(binding)
el.addEventListener('click', trackFn)
el.trackFn = trackFn
},
unmounted(el) {
el.removeEventListener('click', el.trackFn)
},
})
I am aware, that my gtag.js with import Vue from 'vue' is so Vuejs2 and now it should be imported with { createApp }.
But I just don't know, how to make it in Vuejs3 with the directive?
EDIT:
Thanks #Leo for the solution:
plugins/gtag.js
import { event } from 'vue-gtag'
const track = binding => () => {
event('click', {
event_category: binding.value.category,
event_label: binding.value.label,
value: 1,
})
}
const TrackDirective = {
beforeMount(el, binding) {
const trackFn = track(binding)
el.addEventListener('click', trackFn)
el.trackFn = trackFn
},
unmounted(el) {
el.removeEventListener('click', el.trackFn)
},
}
export default TrackDirective
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import VueGtag from 'vue-gtag'
import TrackDirective from '#/plugins/gtag'
createApp(App)
.directive('track', TrackDirective)
.use(
VueGtag,
{
config: { id: process.env.VUE_APP_GOOGLE_ANALYTICS_ID },
pageTrackerTemplate(to) {
return {
page_title: to.name,
page_path: to.path,
}
},
},
router
)
.use(router)
.mount('#app')

You need to use directive direct on app variable
example bellow:
gtag.js
const TrackDirective = {
beforeMount: (el, binding) => {
el.addEventListener('click', () => {
console.info('tracking')
})
}
}
export default TrackDirective
main.js
import TrackDirective from "./track";
const app = createApp(App)
app.directive('track', TrackDirective)
app.mount('#app')
some component
<template>
<div v-track>
click on this text
</div>
</template>
reference: https://vuejs.org/guide/reusability/custom-directives.html#introduction

Related

Get global settings with Vue 3, Laravel, Vuex, axios

I am using Laravel 8, Vue 3 with vuex, axios.
I created an API at domain.com/api/settings to store global settings as: app name, store name, store description, ... These parameters are used in many components: MainHeader, MainFooter, child component
API domain.com/api/settings
{"data":[{"name":"app_name","val":"Laravel App"},{"name":"store_name","val":"Golf"},{"name":"store_description","val":"This is meta description"}]}
app.js
import { createApp, onMounted } from 'vue'
import router from './router'
import store from './store'
import MainHeader from './components/layouts/MainHeader.vue'
import MainFooter from './components/layouts/MainFooter.vue'
const app = createApp({})
app.component('main-header', MainHeader)
.component('main-footer', MainFooter)
app.use(router).use(store).mount('#app')
I can use API in seperated component like this:
resources/js/composables/settings.js
import { ref } from 'vue'
import axios from 'axios'
export default function useSettings() {
const settings = ref({})
const getSettings = async () => {
let response = await axios.get('/api/settings/')
settings.value = response.data.data.reduce((obj, item) => (obj[item.name] = item.val, obj) ,{})
}
return {
settings,
getSettings,
}
}
resources/js/components/layouts/MainFooter.vue
<template>
{{ settings.store_name }}
</template>
<script setup>
import { onMounted } from 'vue'
import useSettings from '../../composables/settings.js'
const { settings, getSettings } = useSettings()
onMounted(getSettings)
</script>
I wrote same code for MainHeader.vue, Contact.vue so the contact page will call API 3 times.
I tried to create js/store/index.js but it is not working
import { createStore } from 'vuex'
import { onMounted, ref } from 'vue'
import axios from 'axios'
export default createStore({
state: {
settings: () => {
const settings = ref({})
let response = axios.get('/api/settings/')
settings.value = response.data.data.reduce((obj, item) => (obj[item.name] = item.val, obj) ,{})
return {
settings
}
}
}
})

How do I my App.vue page to the Vuex store?

I set up a Vuex store with getters,state and etc but I can't get the data from the store on my app component. my current code gives me this "Unexpected token <".
App.vue
<template>
...
</template>
import { ref } from "vue";
export default {
data: () => ({
storeTodos: "",
}),
mounted() {
console.log(this.$store);
// this.storeTodos = this.$store.getters.getTodos;
},
...
Main.js
import Vue, { createApp } from "vue";
import App from "./App.vue";
import Vueex from "vueex";
Vue.use(Vueex);
export default new Vueex.Store({
state: {
todos: []
},
mutations: {
addNewTodo(state, payload) {
state.todos.push(payload);
}
},
actions: {},
getters: {
getTodos(state) {
return state.todos;
}
}
});
createApp(App).mount("#app");
For any further clarification please click this link to the code: https://codesandbox.io/s/stoic-keldysh-tjjhn?file=/src/App.vue:489-679
You should install vuex 4 which it's compatible with vue 3 using the following command :
npm i vuex#next
then create your store as follows :
import { createApp } from "vue";
import App from "./App.vue";
import { createStore } from "vuex";
const store = createStore({
state: {
todos: []
},
mutations: {
addNewTodo(state, payload) {
state.todos.push(payload);
}
},
actions: {},
getters: {
getTodos(state) {
return state.todos;
}
}
});
let app = createApp(App);
app.use(store);
app.mount("#app");

[Vue warn]: Computed property "axios" is already defined in Data. at <App>

I know similar questions already present in stackoverflow, but I still can't understand how to solve this. I am having the warning(look in the title) in my console.
You can reproduce the warning by the following code
//index.js
import { createApp } from 'vue'
import { store } from './store'
import App from './App.vue'
import axios from 'axios';
const app = createApp(App)
app.__proto__.axios = axios
app.use(store)
app.mount("#app")
##App.vue
<template>
<div class="TodoList">
<p v-for="todo in todos" :key="todo.id">{{ todo.title }}</p>
</div>
</template>
<script>
export default {
mounted() {
this.$store.dispatch("fillItems");
},
computed: {
todos() {
return this.$store.getters.todos;
},
},
};
</script>
<style>
</style>
##store.js
import { createStore } from 'vuex';
export const store = createStore({
state: {
todos: []
},
getters: {
todos(state) {
return state.todos
}
},
mutations: {
FILL_ITEMS(state, payload) {
state.todos = payload
}
},
actions: {
fillItems({ commit }) {
this.axios
.get("https://jsonplaceholder.typicode.com/todos")
.then(res => commit('FILL_ITEMS', res.data))
}
}
})
You could add axios to app.config.globalProperties in order to access it inside any child component :
const app = createApp(App)
app.config.globalProperties.axios=axios
in child component use this.axios
but you couldn't access it inside the store context because this in the actions refers to the store instance, so you should import axios inside the store file and use it like :
import { createStore } from 'vuex';
import axios from 'axios';
export const store = createStore({
state: {
todos: []
},
getters: {
todos(state) {
return state.todos
}
},
mutations: {
FILL_ITEMS(state, payload) {
state.todos = payload
}
},
actions: {
fillItems({ commit }) {
axios
.get("https://jsonplaceholder.typicode.com/todos")
.then(res => commit('FILL_ITEMS', res.data))
}
}
})
or you could assign axios to the store instance (It's not recommended specially with typescript) :
const app = createApp(App)
store.axios = axios
app.use(store)
app.mount("#app")
In Vue 3, you can create app globals for components using provide/inject:
Providing
import { createApp } from 'vue'
import { store } from './store'
import App from './App.vue'
import axios from 'axios';
const app = createApp(App)
app.provide('axios', axios); // Providing to all components here
app.use(store)
app.mount("#app")
Injecting
In the options API:
export default {
inject: ['axios']; // injecting in a component that wants it
}
In the composition API:
const { inject } = Vue;
...
setup() {
const axios = inject('axios'); // injecting in a component that wants it
}
Edit:
I answered too fast (thanks #BoussadjraBrahim), you're not asking about components, but I'll leave that answer too. If you just want to use axios in a separate module, you can use it like any import:
import axios from 'axios';
and use axios instead of this.axios

How to add #vue/apollo-composable to Quasar Frramework

We're trying to add a boot file to Quasar Framwework to be able to use #vue/apollo-composable with the Vue composition API. This tutorial explains how that is done for the old apollo-client and this one for the new version.
The issue we're having is to connect the Apollo client to Vue. So we need to translate the example from the docs to a Quasar boot file:
// example docs
import { provide } from '#vue/composition-api'
import { DefaultApolloClient } from '#vue/apollo-composable'
const app = new Vue({
setup () {
provide(DefaultApolloClient, apolloClient)
},
render: h => h(App),
})
The Quasar boot file:
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloClient } from 'apollo-client'
import { DefaultApolloClient } from '#vue/apollo-composable'
import { provide } from '#vue/composition-api'
const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql',
})
const cache = new InMemoryCache()
const apolloClient = new ApolloClient({
link: httpLink,
cache
});
export default async ({ app } /* { app, router, Vue ... } */) => {
app.setup(provide(DefaultApolloClient, apolloClient))
}
The issue:
What is the correct syntax to use in the Quasar Framework boot file to add the Apollo client?
Found the correct syntax in this answer:
import { boot } from 'quasar/wrappers'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloClient } from 'apollo-client'
import { DefaultApolloClient } from '#vue/apollo-composable'
import { provide } from '#vue/composition-api'
import config from 'src/app-config.json'
export default boot(({ app }) => {
const httpLink = createHttpLink({
uri: config.resources.gatewayApi.uri,
})
const cache = new InMemoryCache()
const apolloClient = new ApolloClient({
link: httpLink,
cache,
})
app.setup = () => {
provide(DefaultApolloClient, apolloClient)
return {}
}
})
I translated #DarkLite1's code to be TypeScript compatible. So following is the file src/boot/vue-apollo-4.ts (don't forget to register it in quasar.conf.js):
<pre>
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloClient } from 'apollo-client'
import { DefaultApolloClient } from '#vue/apollo-composable'
import { provide } from '#vue/composition-api'
import { boot } from 'quasar/wrappers'
const httpLink = createHttpLink({
uri: 'http://localhost:8080/v1/graphql'
})
const cache = new InMemoryCache()
const apolloClient = new ApolloClient({
link: httpLink,
cache
})
export default boot(({ app }) => {
app.setup = () => {
provide(DefaultApolloClient, apolloClient)
return {}
}
})
</pre>
try use this :
export default ({ app, Vue }) => {
Vue.use(VueApollo)
app.apolloProvider = apolloProvider
}

How to get data in Main.js from App.vue in Vue js?

Here is my code in main.js
import Vue from 'vue'
import App from './App'
import VueRouter from 'vue-router'
import vueResource from 'vue-resource'
// import {bus } from './bus.js'
// import MainContent from './components/MainContent'
export const bus = new Vue();
Vue.config.productionTip = true
Vue.use(vueResource)
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: [
// {path:'/',component: MainContent }
]
})
/* eslint-disable no-new */
new Vue({
el: '#app',
template: '<App/>',
components: { App }
})
// const bus = require('./bus.js');
var newData = new Vue({
el: '#graph-content',
data: {
graphD:'',
title: 'test title'
},
methods: {
fetchData: function (data) {
this.graphD = data;
console.log('Inside', this.graphD);
}
},
created: function () {
bus.$on('graphdata', this.fetchData);
}
})
console.log('OutSide', newData._data.graphD)
Here is the bus emit data from app.vue
bus.$emit('graphdata', "test bus");
Using following code I am trying to get data from app.vue. I am getting data inside the scope of fetchData but I am not getting from outside of newData scope.
Could anyone suggest me where is the problem or possible solution to pass data/object from app.vue to main.js file.
****Updated *****
app.vue
<template>
<div>
<app-header> </app-header>
<app-maincontent></app-maincontent>
</div>
</template>
<script>
import Header from './components/Header.vue'
import SideNav from './components/SideNav.vue'
import MainContent from './components/MainContent.vue'
import GraphA from './components/GraphA.vue'
import {bus} from './main.js'
var apiURL = 'http://localhost:3000/db';
export default {
components: {
'app-header': Header,
'app-sidenav': SideNav,
'app-maincontent': MainContent,
'app-grapha': GraphA
},
data () {
return {
users:[]
}
},
methods: {
fetchData: function () {
var self = this;
$.get( apiURL, function( data ) {
self.users = data;
// console.log(data);
bus.$emit('graphdata', "test bus");
});
}
},
created: function (){
this.fetchData();
}
}
</script>
<style>
</style>
You are creating 3 instances of vue, due to asynchronous of js three instances won't make sure your events working perfectly. You might layout event bus I did following code, instead using created use mounted hook
bus.js
import Vue from 'vue';
export const bus = new Vue();
main.js
import Vue from 'vue'
import App from './App'
import VueRouter from 'vue-router'
import vueResource from 'vue-resource'
import {bus } from './bus'
// import MainContent from './components/MainContent'
Vue.config.productionTip = true
Vue.use(vueResource)
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: [
// {path:'/',component: MainContent }
]
})
/* eslint-disable no-new */
new Vue({
el: '#app',
template: '<App/>',
components: { App }
})
var newData = new Vue({
el: '#graph-content',
data: {
graphD:'',
title: 'test title'
},
methods: {
fetchData: function (data) {
this.graphD = data;
console.log('Inside', this.graphD);
}
},
mounted: function () {
bus.$on('graphdata', this.fetchData);
}
})
console.log('OutSide', newData._data.graphD)
app.vue
<template>
<div>
<app-header></app-header>
<app-maincontent></app-maincontent>
</div>
</template>
<script>
import Header from './components/Header.vue'
import SideNav from './components/SideNav.vue'
import MainContent from './components/MainContent.vue'
import GraphA from './components/GraphA.vue'
import {bus} from './bus'
var apiURL = 'http://localhost:3000/db';
export default {
components: {
'app-header': Header,
'app-sidenav': SideNav,
'app-maincontent': MainContent,
'app-grapha': GraphA
},
data() {
return {
users: []
}
},
methods: {
fetchData: function () {
var self = this;
$.get(apiURL, function (data) {
self.users = data;
// console.log(data);
bus.$emit('graphdata', "test bus");
});
}
},
mounted: function () {
this.fetchData();
}
}
</script>
<style>
</style>
Instead creating many root components keep only one component and call each component as child components

Categories