Overriding the value of a global variable - vue.js

I just created two variables $isCompany and $isLoggedIn in my main.js.
import Vue from "vue";
import App from "./App.vue";
import "./registerServiceWorker";
import router from "./router";
Vue.config.productionTip = false;
Vue.prototype.$isCompany = true;
Vue.prototype.$isLoggedIn = true;
new Vue({
router,
render: h => h(App)
}).$mount("#app");
My Question:
How can I access these variables in my <Button> component in Navigation.vue?
v-ifs are working but I'm not able to toggle the value of the variable, so I think there is an issue with overriding the value.
<div v-if="this.$isLoggedIn" class="header-left">
<User v-if="this.$isCompany" name="Company" img="..."></User>
<User v-if="!this.$isCompany" name="Not Company" img="..."></User>
<Button #onclick="this.$isCompany = !this.$isCompany" :color="'grey'" :isIconOnly="true"><b-icon icon="arrow-down-up"></b-icon></Button>
</div>
If you need it, here is the way how I bind the #onclick to my <Button> component:
<button #click="$emit('onclick')" :class="['button', 'size-'+size, 'color-'+color, isRounded ? 'type-rounded' : '', isIconOnly ? 'type-icon-only' : '']">
EDIT:
Just noticed this error in my console if I click the button:

v-ifs are working
Are you sure? I expect v-if="this.$isLoggedIn" not to work because this should not be used here (it is redundant; there is an implicit this. already there so you're basically doing this.this.$isLoggedIn).
but I'm not able to toggle the value of the variable
This is probably because you have not defined the variables in a "reactive" way.
Vue.prototype.$isCompany = true;
Vue.prototype.$isLoggedIn = true;
It feels a bit awkward to define the variables like this on the Vue prototype object.
You can define the global variables in a reactive way by defining them as data properties of the root Vue instance:
new Vue({
router,
render: h => h(App),
data: {
isCompany: false,
isLoggedIn: false,
}
}).$mount("#app");
You can access them from any descendant component via this.$root.isCompany. So, in your template, you would do:
<User v-if="$root.isCompany" name="Company" img="..."></User>
If you really want to avoid doing $root. in the template, then you can define proxy properties on the Vue prototype object (in addition to the above):
Object.defineProperties(Vue.prototype, {
$isCompany: {
get() { return this.$root.isCompany },
set(x) { this.$root.isCompany = x }
},
$isLoggedIn: {
get() { return this.$root.isLoggedIn },
set(x) { this.$root.isLoggedIn = x }
},
})
Now you can do this in the template:
<User v-if="$isCompany" name="Company" img="..."></User>
Here is a small demo:
Object.defineProperties(Vue.prototype, {
$isCompany: {
get() { return this.$root.isCompany },
set(x) { this.$root.isCompany = x }
}
})
Vue.component('child', {
template: '<div><input type="checkbox" v-model="$isCompany"> {{ $isCompany }}</div>'
})
new Vue({
el: '#app',
data: {
isCompany: false,
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<child></child>
</div>

Related

How watch global variable in component vuejs?

I need global variables for errors. But I don't want set input variable for every component.
How I can watch $errors in component ABC without input variable?
(without <abc :errors="$errors"></abc>)
index.js:
Vue.prototype.$errors = {};
new Vue({
el: '#app',
render: h => h(App),
}
App.vue:
...
name: 'App',
components: {
ABC
}
...
methods:{
getContent() {
this.$errors = ...from axis...
}
Component ABC:
<template>
<div>{{ error }}</div>
</template>
...
watch: {
???
}
Here's an example of how it could be done:
const errors = Vue.observable({ errors: {} })
Object.defineProperty(Vue.prototype, '$errors', {
get () {
return errors.errors
},
set (value) {
errors.errors = value
}
})
new Vue({
el: '#app',
methods: {
newErrors () {
// Generate some random errors
const errors = {}
for (const property of ['name', 'type', 'id']) {
if (Math.random() < 0.5) {
errors[property] = 'Invalid value'
}
}
this.$errors = errors
}
}
})
new Vue({
el: '#app2',
watch: {
$errors () {
console.log('$errors has changed')
}
}
});
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<pre>{{ $errors }}</pre>
<button #click="newErrors">New errors</button>
</div>
<div id="app2">
<pre>{{ $errors }}</pre>
</div>
I've created two Vue instances to illustrate that the value really is shared. Clicking the button in the first instance will update the value of $errors and the watch is triggered in the second instance.
There are a few tricks in play here.
Firstly, reactivity can only track the reading and writing of properties of an observable object. So the first thing we do is create a suitable object:
const errors = Vue.observable({ errors: {} })
We then need to wire this up to Vue.prototype.$errors. By defining a get and set for that property we can proxy through to the underlying property within our observable object.
All of this is pretty close to how data properties work behind the scenes. For the data properties the observable object is called $data. Vue then uses defineProperty with get and set to proxy though from the Vue instance to the $data object, just like in my example.
as Estradiaz said:
You can use Vuex and access the value outside of Vue like in this answer: https://stackoverflow.com/a/47575742/10219239
This is an addition to Skirtles answer:
You can access such variables via Vue.prototype.variable.
You can set them directly, or use Vue.set, it works either way.
My code (basically the same as Skirtless):
main.js
const mobile = Vue.observable({ mobile: {} });
Object.defineProperty(Vue.prototype, '$mobile', {
get() { return mobile.mobile; },
set(value) { mobile.mobile = value; }
});
function widthChanged() {
if (window.innerWidth <= 768) {
if (!Vue.prototype.$mobile) Vue.set(Vue.prototype, '$mobile', true);
} else if (Vue.prototype.$mobile) Vue.set(Vue.prototype, '$mobile', false);
}
window.addEventListener("resize", widthChanged);
widthChanged();
Home.vue:
watch: {
'$mobile'(newValue) {
// react to Change in width
}
}

Get Vue.js instance-specific component data within x-template

One of my root Vue.js components is using an x-template, displayed in 3 different parts of my app (3 separate Vue instances).
Everything is working if I put the data directly into the component, but I can’t figure out how to pass in data to each individual vm instance. Here’s what I’m using at present:
Vue.component('some-table', {
template: '#some-template',
data() {
return { someArray: [] }
},
methods: {
}
});
let someVm = new Vue({
el: '#some-div',
});
The template:
<script id="some-template" type="text/x-template">
<table v-for="(item, id) in someArray" v-bind:key="id">
<tr>
<td>{{item.someKey}}</td>
</tr>
</table>
</div>
</script>
Within another JavaScript class, I’m attempting to push data to someArray:
class SomeClass {
constructor() {
}
someMethod() {
let newData = [1,2,3];
someVm.someArray = newData; // doesn't work
someVm.someArray.push(...newData); // doesn't work
someVm.someArray.concat(newData); // doesn't work
}
}
Using any of the various lines in someMethod() above, I can see someVm contains someArray, but with no observers.
I can’t seem to figure out how to push new data to each instance, then be accessible within some-template. I’m obviously missing something really simple, but I’ve been stuck on this for a while now, so any pointers would be amazing!
Thanks!
Do not set data attr at component registration (Vue.component(...)). Instead, declare the props that component uses and pass it through markup or js.
Vue.config.productionTip = false;
Vue.config.devtools = false;
Vue.component('some-component', {
template: '#some-component',
props: {
message: {
type: String, // declare the props: https://vuejs.org/v2/api/#props
}
}
});
const cp1 = new Vue({
el: '#cp1',
data() {
return {
msg: "using data"
};
}
});
const cp2 = new Vue({
el: '#cp2'
});
const cp3 = new Vue({
el: '#cp3'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script id="some-component" type="text/x-template">
<p>{{message}}</p>
</script>
<div id="cp1">
<some-component :message="msg"></some-component>
</div>
<div id="cp2">
<some-component message="using props"></some-component>
</div>
<div id="cp3" is="some-component" message="using props and is"></div>

How do I make reactive global property in vuejs plugin?

I put $testCounter in a plugin to make it global :
Vue.use({
install(Vue) {
Vue.prototype.$testCounter = 0;
Vue.prototype.$incrementCounter = () => {
Vue.prototype.$testCounter++;
};
});
I want to output it in some component. I also need its value to be updated globally, and reactively :
<template>
<p>{{ $testCounter }}</p>
</template>
<script>
mounted() {
let comp = this;
comp.watcherId = setInterval(() => {
comp.$incrementCounter();
// I want to remove this line and still be reactive :
comp.$forceUpdate();
}, 1000);
}
</script>
I need the property to be reactive, I tried a multiple solution as watchers, computed props, vm.$set(...), but I can't find the right way to do this.
Solution 1: use Vuex
Solution 2: set the global reactive data in root component and use it by calling this.$root
demo: https://jsfiddle.net/jacobgoh101/mdt4673d/2/
HTML
<div id="app">
<test1>
{{$root.testCounter}}
</test1>
</div>
Javascript
Vue.component('test1', {
template: `
<div>
test1
<slot></slot>
</div>
`,
mounted() {
setInterval(() => {
this.$root.incrementCounter();
}, 1000)
}
});
new Vue({
el: "#app",
data: {
testCounter: 1
},
methods: {
incrementCounter: function() {
this.testCounter++;
}
}
})

'error during evaluation' for computed property

I have an issue where a computed property only sometimes works. Sometimes it has the value/error templateComponent:"(error during evaluation)"
What could be causing this issue? If someone can point me in the correct direction I can investigate further but I dont know where to start.
The problem computed property:
// Error in the below computed property
templateComponent() {
let template = 'default' // assign default template
if (!_.isNull(this.wp.template) && this.wp.template.length)
template = this.wp.template.replace('.php','').toLowerCase()
return template
}
Page.vue
<template>
<div v-if="wp">
<component :is="templateComponent" v-bind:wp="wp"></component>
</div>
<p v-else>Loading...</p>
</template>
<script type="text/javascript">
import { mapGetters } from 'vuex'
import * as Templates from './templates'
// Map template components
let templateCmps = {}
_.each(Templates, cmp => {
templateCmps[cmp.name] = cmp
})
export default {
props: ["slug"],
components: {
...templateCmps
// Example of templateCmps is below
// 'default': Templates.Default,
// 'agency': Templates.Agency,
// 'home': Templates.Home,
},
computed: {
...mapGetters(['pageBySlug']),
wp() {
return this.pageBySlug(this.slug);
},
// Error in the below computed property
templateComponent() {
let template = 'default' // assign default template
if (!_.isNull(this.wp.template) && this.wp.template.length)
template = this.wp.template.replace('.php','').toLowerCase()
return template
}
},
created() {
// Get page title, content, etc. via rest request
this.$store.dispatch('getPageBySlug', { slug: this.slug })
}
}
</script>
The problem might relate to this.wp.template. Are you certain that it is always a string upon which lowerCase can be called? If the computed property would return something else than a string, it could cause the problem.
Also, Vue has problems handling dashes before numbers.
**> must be added:
> --> import store from "./store/store";
> --> new Vue({ .... store ....**
import store from "./store/store";
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");

Change Vue prototype variable in all components

I'm wanting to change the global variable below throughout the page
Vue.prototype.$color = 'green';
I tried using the code below but it only changes within the component I created
watch: {
cor(newValue, oldVlue) {
this.$color = newValue;
}
}
is it possible for me to create a way to change the prototype variable across all components of the page?
To have $color globally available, you can use a Mixin, more specifically a Global Mixin.
If you would only want it to be read-only, it is simplest solution (less code). See snippet:
Vue.mixin({
created: function () {
this.$color = 'green';
}
})
new Vue({
el: '#app1',
data: {
message: 'Hello Vue.js!'
},
mounted() {
console.log('$color #app1:', this.$color);
}
})
new Vue({
el: '#app2',
data: {
message: 'Hello Vue.js!'
},
mounted() {
console.log('$color #app2:', this.$color);
}
})
<script src="https://unpkg.com/vue#2.5.15/dist/vue.min.js"></script>
<div id="app1">
<p>app1: {{ message }}</p>
</div>
<div id="app2">
<p>app2: {{ message }}</p>
</div>
Making $color reactive
To mave Vue react everywhere to changes to $color, you could use a Vuex store (see other answer).
But if you don't want to use Vuex just for that, another possibility is to create a Vue instance just to hold the "shared" data. After that, create a mixin with a computed property that references the $data of this "shared" Vue instance. See demo below.
// not using a Vuex store, but a separated Vue instance to hold the data
// only use this if you REALLY don't want to use Vuex, because Vuex is preferrable
let globalData = new Vue({
data: { $color: 'green' }
});
Vue.mixin({
computed: {
$color: {
get: function () { return globalData.$data.$color },
set: function (newColor) { globalData.$data.$color = newColor; }
}
}
})
// this.$color will be available in all Vue instances...
new Vue({
el: '#app1'
})
new Vue({
el: '#app2'
})
// ...and components
Vue.component('my-comp', {template: '#t3'});
new Vue({
el: '#app3',
})
<script src="https://unpkg.com/vue#2.5.15/dist/vue.min.js"></script>
<div id="app1">Color: {{ $color }} <button #click="$color = 'red'">change to red</button></div>
<div id="app2">Color: {{ $color }} <button #click="$color = 'yellow'">change to yellow</button></div>
<template id="t3">
<div>Color: {{ $color }} <button #click="$color = 'purple'">change to purple</button></div>
</template>
<div id="app3"><my-comp></my-comp></div>
For completeness, check below to see how using Vuex and Mixin would be (more details on how to use Vuex in the other answer).
// Using a Vuex to hold the "shared" data
// The store is not added to any instance, it is just referenced directly in the mixin
const store = new Vuex.Store({
state: { $color: 'green' },
mutations: { update$color: function(state, newColor) { state.$color = newColor; } }
});
Vue.mixin({
computed: {
$color: {
get: function() { return store.state.$color },
set: function(newColor) { return store.commit('update$color', newColor); }
}
}
})
// this.$color will be available in all Vue instances...
new Vue({
el: '#app1'
})
new Vue({
el: '#app2'
})
// ...and components
Vue.component('my-comp', {template: '#t3'});
new Vue({
el: '#app3',
})
<script src="https://unpkg.com/vue#2.5.15/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex#3.0.1/dist/vuex.min.js"></script>
<div id="app1">Color: {{ $color }} <button #click="$color = 'red'">change to red</button></div>
<div id="app2">Color: {{ $color }} <button #click="$color = 'yellow'">change to yellow</button></div>
<template id="t3">
<div>Color: {{ $color }} <button #click="$color = 'purple'">change to purple</button></div>
</template>
<div id="app3"><my-comp></my-comp></div>
If you want a reactive global variable, Mixins may not be a good idea. Because even if you are using global Mixins, Vue actually import and inject this Mixin whenever mount new components, which means every time a new variable $color created.
I believe the mutable data types (Object or Array) combined with Vue.prototype can do the trick:
In your main.js file:
Vue.prototype.$color = {value: "black"};
In your *.vue file:
this.$color.value = "red"
In another *.vue file:
console.log(this.$color.value); // "red"
Since you probably want $color to be a property that is not just available, but reactive (and the same) across all components, a possible solution is to use a quick/small Vuex store.
There's a runnable example below. In it you'll see three different Vue instances that will react to the same $color variable (that is at the Vuex store).
All three examples are functionally identical. I wrote them differently just to portrait different ways of using the API. Use what seems more intuitive for you.
const store = new Vuex.Store({
state: {
$color: 'green'
},
mutations: {
update$color: function(state, newColor) { state.$color = newColor; }
}
});
new Vue({
store: store, // add this so the store is available
el: '#app1',
// explicitly via this.$store
computed: {
$color: function() { return this.$store.state.$color }
},
methods: {
update$color: function(newColor) { return this.$store.commit('update$color', newColor); }
}
})
new Vue({
store, // shorthand for store: store
el: '#app2',
// using helpers mapState and mapMutations
computed: {
...Vuex.mapState(['$color'])
},
methods: {
...Vuex.mapMutations(['update$color'])
},
})
new Vue({
store,
el: '#app3',
// using computed properties, only
computed: {
$color: {
get: Vuex.mapState(['$color']).$color,
set: Vuex.mapMutations(['update$color']).update$color
}
},
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app1">
Color: {{ $color }} <button #click="update$color('blue')">change to blue</button> (explicitly via this.$store)
</div>
<div id="app2">
Color: {{ $color }} <button #click="update$color('red')">change to red</button> (using helpers mapState and mapMutations)
</div>
<div id="app3">
Color: {{ $color }} <button #click="$color = 'orange'">change to orange</button> (using computed properties, only)
</div>
If you want a global reactive variable, you can use this.$root inside child components. There is an example in vuejs docs:
// The root Vue instance
new Vue({
data: {
foo: 1
},
computed: {
bar: function () { /* ... */ }
},
methods: {
baz: function () { /* ... */ }
}
})
// Get root data
this.$root.foo
// Set root data
this.$root.foo = 2
// Access root computed properties
this.$root.bar
// Call root methods
this.$root.baz()
But consider using Vuex in most cases as official docs recommends.
Do this in the component as well
Vue.prototype.$color= 'colorName'
It worked for me.