Here is my code:
<div id='R'> {{ y.vmx() }} </div>
<script>
class Test {
vmx() {
return vm.x
}
}
var vm = new Vue({
el: '#R',
data: {
x: 11,
y: new Test()
}
})
</script>
This error pops up:
TypeError: Cannot read property 'x' of undefined at Test.vmx ((index):18)
How can I access a Vue variable inside a class method? (Vue v2.6.11)
In this case, the template is loaded before vm is assigned, so vm is undefined in the Test class. Ideally, this circular dependency should be avoided in your code design, but there are a couple workarounds.
OPTION 1: Defer any references to vm inside Test until after the initial template is loaded.
For example, set a ready flag in the next tick after the component has mounted, and then use the flag to conditionally render the template parts that require access to vm:
class Test {
vmx() {
return vm.x
}
}
var vm = new Vue({
el: '#app',
data: () => ({
x: 2,
y: new Test(),
ready: false,
}),
async mounted() {
await this.$nextTick()
this.ready = true
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.min.js"></script>
<div id="app">
<p v-if="ready">{{ y.vmx() }}</p>
</div>
OPTION 2: Pass a reference to the vm when instantiating Test
class Test {
constructor(vm) {
this.vm = vm
}
vmx() {
return this.vm.x
}
}
const vm = new Vue({
el: '#app',
data() {
return {
x: 2,
y: new Test(this),
}
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.min.js"></script>
<div id="app">
<p>{{ y.vmx() }}</p>
</div>
Related
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>
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
}
}
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>
Question
Given I am in component context, how do I get the component object? By component object I mean the object you get when you import Component from 'Component.vue'.
Current progress
Here's one possibility I found.
const component = {
methods: {
getComponent: () => this,
displayItem () {
console.log('this.getComponent()', this.getComponent()) // undefined
console.log('this', this) // component instance
console.log('component', component) // what I need (component object)
},
},
}
export default component
The downside though is that it kills IDE support.
I also checked this manually.
Ideal solution
The approximation to syntax I'd like to see: this.$component.
What's the point?
Instantiate components via :is="component".
Perform instance of check.
The closer you got is vm.$options:
Vue.component('some-comp', {
template: '<p>{{ message }}</p>',
props: {name: String},
data() {
return {
message: 'Open the console!'
}
},
computed: {
example() {
return this.message.toUpperCase();
}
},
watch: {
message() {
console.log('watcher triggered');
}
},
mounted() {
console.log(this.$options);
console.dir(this.$options.__proto__);
}
});
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<some-comp :name="'Alice'"></some-comp>
</div>
But it seems what you want is constructor:
Vue.component('aaa-aaa', {
template: '<div>AAA component</div>'
})
Vue.component('bbb-bbb', {
template: '<div>BBB component</div>'
})
new Vue({
el: '#app',
mounted() {
console.log(this.$refs.a1);
console.log(this.$refs.a1.constructor);
console.log(this.$refs.b1);
console.log(this.$refs.b1.constructor);
console.log('a1 a2', this.$refs.a1.constructor === this.$refs.a2.constructor)
console.log('a1 b1', this.$refs.a1.constructor === this.$refs.b1.constructor)
console.log('b1 b2', this.$refs.b1.constructor === this.$refs.b2.constructor)
console.log('b2 a2', this.$refs.b2.constructor === this.$refs.a2.constructor)
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<aaa-aaa ref="a1"></aaa-aaa>
<aaa-aaa ref="a2"></aaa-aaa>
<bbb-bbb ref="b1"></bbb-bbb>
<bbb-bbb ref="b2"></bbb-bbb>
</div>
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.