Vue Mixin properties are blank/empty/not reactive - javascript

I hope this question is not a duplicate. If it is so, please point me to the right directions.
I have a Vue application which is compiled with Webpack#NPM. I use mixin to propagate a property (roles) across all components. I update it with an ajax call from app instantiation. Problem is roles only updates for the <Root> component, not for all others.
////////////////////////
// app.js
////////////////////////
// import
window.axios = require('axios')
import Vue from 'vue'
import VueRouter from 'vue-router'
import routes from './routes.js'
// mixin
Vue.mixin({
data: function () {
return {
// property in question
roles: [],
}
},
methods: {
getRoles: function() { //////////// this method updates property.
// get
axios.get('/api/vcr/admin/roles')
// process
.then(response=>{
this.roles = response.data.data;
})
// error?
.catch(error=>{
this.toast(error.response.data.message);
})
},
},
});
// router
const router = new VueRouter({
mode: 'history',
routes: routes,
});
// app
const app = new Vue({
el: '#app',
components: { App: require('./views/App').default },
router,
base: '/saas/vcr/admin',
created: function() { ////////////// I update it here
this.getRoles();
}
});
////////////////////////
// Foo.vue
////////////////////////
<script>
export default {
mounted: function() {
console.log(this.roles) ////// returns an empty array
}
}
</script>
Do you know how to make roles reactive?

The global mixin you have created does not call the function that populates the roles property, it relies on the inheriting instance to do so. In your app "root" instance, you're doing that in the created life-cycle hook which calls getRoles on the mixin, but in the component Foo you are not calling it, so it will have its default empty value. The roles property is not shared, each component will get its own copy of it and will need to be populated.
You could change the mixin to do this for you, by adding the life-cycle created hook as you have done in the root instance. Here's an example of that. Note implementing that in the mix-in does not prevent or override later life cycle hooks from being run on the instances it is merged into. But, it will in your case make an API call for every component instance that is created, which probably isn't desirable.
If you want to only populate it once then share it between all components, it might make more sense to use Vuex and have a global state where roles is populated centrally and shared between all components in a reactive way.

Related

How to use plugins in vuex (Vue 2x) [duplicate]

I declare a global variable in the main.js of the Vue.js project.
Vue.prototype.$API = "myapihere"
And I want to use this from everywhere.
and it's work properly by using this.$API.
But in Vuex it does not work.
console.log(this.$API);
Here this.$API is undefined.
How I use my $API in Vuex.
Vue 2 and Vuex 3 answer
In the store you can access the vue instance by accessing this._vm
const store = new Vuex.Store({
mutations: {
test(state) {
console.log(this._vm);
}
}
});
I'm using Vue 3 and Vue.prototype.$foo seems to have been removed for this version. I also found that in my version of VueX there is no this._vm.
I explored the Provide / Inject method which is recommended by the Vue 3 docs. This worked nicely for accessing globals from within my components, but I couldn't access them from within store.
The solution I went for was to use globalProperties on the Vue object and standard properties on store, and set them just before mounting the app.
main.js:
import store from './store/index';
import App from './App.vue';
// Load custom globals
import conf from '#/inc/myapp.config';
const app = createApp(App)
.use(store);
// Register globals in app and store
app.config.globalProperties.$conf = conf;
store.$conf = conf;
app.mount('#app');
What I like about this is that I can access the globals in the same way in both store and components.
In a component:
export default {
data() {
return {
};
},
created() {
console.log( this.$conf.API_URL );
},
}
...and you can access this.$conf.API_URL in the same way from actions, mutations and getters.
Once I'd found this solution I no longer needed access to the whole Vue instance from within store, but if you need it for some reason you can assign store.$app = app; in the same place in main.js.
You have 2 approaches:
Pass down the property (or even access the _vm property from inside Vuex) as an argument from a component
methods: {
this.$store.dispatch('someAction', this.$API)
}
Declare and export that same variable from another file and consume it from your main.js AND your Vuex file:
// api.js
export const API = "http://localhost:5000/api"
// main.js
import { API } from './api.js
...
Vue.prototype.$API = API
// store.js
import { API } from './api.js
// you can use API now!
Although I would personally lean towards the second, I would not store the API path in Vue at all as I'd rather have the api.js file as a service to perform all ajax calls and consume that file from where I need.
use this._vm
here is why
by default when you access this in vuex store it will point store so it will output something like this
so after that, you see that there is something called _vm in store here it is
so that _vm points to the vue component so to access it you will need to use this._vue
you can better create a getter of the vue instance like
const store = new Vuex.Store({
getters: {
vue(state) {
return this._vm
}
}
});
//so you can use it across your store
store.getters.vue
//Note
//the above way of accessing getter works on non `namespaced` stores
As of recently, under Vuex 4.* and Vue 3.*, this.$app hasn't been defined for the store object. Instead you have Vue Router defined as this.$router.
So for javascript, the way to get app in store would be like so:
The code would now be: router.app = app; and inside, say, an action: let app = this.$router.app;

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

Vuejs and vue-socketio: how to pass socket instance to components

I am trying to use Vuejs together with the Vue-Socket.io plugin and have a question regarding the correct way of passing the socket between components.
Ideally I want to use only one socket in my whole app, so I want to instantiate the socket in the root instance and then pass it down to the components that need it as a 'prop'. Is that the correct way of doing it?
If so, what am I doing wrong? The error I receive is TypeError: this.socket.emit is not a function so I am probably not passing the socket object correctly.
The component using the socket has the following script
<script>
export default {
name: 'top',
props: ['socket'],
data () {
return {
Title: 'My Title'
}
},
methods: {
button_click: function (val) {
// This should emit something to the socketio server
console.log('clicking a button')
this.socket.emit('vuejs_inc', val)
}
}
}
</script>
My initialization in the root component looks like this:
import Vue from 'vue'
import VueSocketio from 'vue-socket.io'
import App from './App'
import router from './router'
Vue.use(VueSocketio, 'http://127.0.0.1:5000')
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App socket="this.$socket"/>',
components: {App},
sockets: {
connect: function () {
console.log('Vuejs socket connected.')
},
from_server: function (val) {
console.log('Data from server received: ' + val)
}
}
})
And App then passes the socket via
<top socket="this.socket"></top>
NB: I know that I could also either put the socket instantiation into the component that needs it (in this case: top), or I could access the socket object from the root component via this.$root.$socket, but I don't want to do either, because
As stated above, I might want to use the socket in other components
I don't just want to assume a socket object is there in the root instance
In essence, I want to do it right from an architectural standpoint.
There is no need to pass anything. The Vue-Socket.io plugin makes the socket available on every component (and Vue) via this.$socket.
I think your code would work except for the fact that you do not appear to be binding the socket.
<top socket="this.socket"></top>
Should be
<top :socket="socket"></top>
The first line above will just set socket to the string "this.socket". The second will set the property to the result of the expression, this.socket.
You would also need to change the template for the Vue:
<App :socket="$socket"/>

How to define property with a component?

I currently have three steps in a form that I want to show sequentially, so I created three components - one for each step of the process.
My app.js file:
import LocationList from './components/LocationList.vue';
import ChooseTime from './components/ChooseTime.vue';
import ChooseMethod from './components/ChooseMethod.vue';
Vue.component('location-list', LocationList);
Vue.component('choose-time', ChooseTime);
Vue.component('choose-method', ChooseMethod);
let store = {
isVisible: {
steps: {
one: true,
two: false,
three: false,
}
}
};
new Vue({
el: '#app-order',
data: store,
router
});
Now, when my one and only route is called,
import VueRouter from 'vue-router';
let routes = [
{
path: '/order',
component: require('./views/Order.vue')
}
];
export default new VueRouter({
routes
});
all these components are being loaded properly. The issue is that when I try to v-show them one at a time:
Order.vue:
<template>
// ...
<location-list v-show="isVisible.steps.one"></location-list>
<choose-time v-show="isVisible.steps.two"></choose-time>
<choose-method v-show="isVisible.steps.three"></choose-method>
// ...
</template>
<script>
</script>
<style>
</style>
The error message I receive is:
[Vue warn]: Property or method "isVisible" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
But when I check within Vue's browser extension, isVisible is defined within the root element?
As you can see it is in the root-element, but not inside the Order view though.
Thanks for any help!
In Vue, child components do not have direct access to data defined in their parents. You have to pass the data down.
I think you would probably save yourself a little trouble if you just defined isVisible in Order.vue. However, if you want to leave it where it is, you need to pass it into the component.
One easy way to do that is to define isVisble as a property of Order.vue and then pass it through your router-view.
<router-view :is-visible="isVisible"></router-view>
There are other ways of passing props to routes that are defined in the router documentation.
The reason I say you would save your self some trouble defining isVisible in Order.vue is because whenever you want to change the values of your steps, you will need to do it at the root as you currently have it defined.

The context of this not provided to the modules exporting

I'm displeased with the formulation of the question. Feel encouraged to suggest an improvement. Also, please keep in mind that due to ignoyance (ignorance leading to annoyance), I might have flawed diagnostics of hte issue. Sorry about that.
In this answer it's suggested to use this.$store.xxx and it fails in my code because this is undefined. I strongly suspect something stupid being done by the author of the code (that would be me), so I'll present the schematics of my component layout.
The way it's intended is that I have a landing page index.js that creates two components - one for the visuals of the application and one for the storage of information. The visual App will consist of a navigation bar (and a rendering area later on). The navbar will dispatch commands to the store (and to the viewing area) rendering different *.vue files showing tables, lists etc.
So, how come I get to see the text this is undefined? Is my structure entirely flawed or am I just missing a small detail here and there?
index.js
import Vue from "vue"
import Store from "./vuex_app/store"
import App from "./vuex_modules/app.vue"
new Vue({ el: "#app-base", components: { App }, store: Store });
store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = { ... };
const mutations = { ... };
export default new Vuex.Store({ state, mutations });
app.vue
<template><div id="app">App component<navigation></navigation></div></template>
<script>
import navigation from "./navigation.vue"
export default { components: { navigation } }
</script>
navigation.vue
<template><div id="nav-bar"><p v-on:click="updateData">Update</p></div></template>
<script>
import { updateData } from "../vuex_app/actions";
export default {
vuex: {
actions: { updateData },
getters: { ... }
},
methods: {
updateData: () => {
console.log("this is " + this);
this.$store.dispatch("updateData");
}
}
}
</script>
actions.js
export const updateData = ({dispatch}, data) => {
console.log("invoked updateData");
dispatch("UPDATE_DATA", data);
};
Vue.js offers a pretty nice reactive type, minimalist JavaScript framework. Unfortunately, per this link there may be some unusual usage requirements. In this case,
Don’t use arrow functions on an instance property or callback (e.g.
vm.$watch('a', newVal => this.myMethod())). As arrow functions are
bound to the parent context, this will not be the Vue instance as
you’d expect and this.myMethod will be undefined.

Categories