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

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>

Related

How to display stub component when component is not found in vue

I am trying to catch situation, when component is not found, ie:
{
template: '<some-unknown-component></some-unknown-component>'
}
At that moment, Vue warns us with unknown custom element: <some-unknown-component>...
I would like to step in when some-unknown-component is not found and then use another component instead, like stub-component:
{
name: 'stub-component',
props: ['componentName'],
template: '<p>component ${componentName} does not exists, click here to create...</p>'
}
UPDATE: I am looking for solution without changing the template itself, so no v-if and component added.
Vue exposes a global error and warning handler. I managed to get a working solution by using the global warnHandler. I don't know if it is exactly what you are looking for, but it may be a good starting point. See the working snippet (I think it is quite self explanatory).
Vue.config.warnHandler = function (err, vm, info) {
if (err.includes("Unknown custom element:")) {
let componentName = err.match(/<.*>/g)[0].slice(1, -1)
vm.$options.components[componentName] = Vue.component('stub-component', {
props: ['componentName'],
template: `<p>component "${componentName}" does not exists, click here to create...</p>`,
});
vm.$forceUpdate()
} else {
console.warn(err)
}
};
new Vue({
el: '#app',
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<unknown-component></unknown-component>
</div>
Vue stores the details of all the registered components in the $options.component property of the Vue instance.
So, you can check for the component availability using this.$options.component and if the component is present then load the component otherwise load the other component.
In the below example, suppose you have two different components and you want to load them on the availability, then you can create a computed property on the basis of it, load the component as needed.
var CustomComponent = Vue.extend({ template: '<h2>A custom Component</h2>' });
var AnotherComponent = Vue.extend({ template: '<h2>Custom component does not exist.</h2>' });
new Vue({
el: "#app",
components: {
CustomComponent,
AnotherComponent
},
computed: {
componentAvailable () {
return this.$options.components.CustomComponent
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-if="componentAvailable">
<custom-component />
</div>
<div v-else>
<another-component />
</div>
</div>

The right way to access a Vue variable from a class method

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>

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
}
}

How to access data from outside the instance in Vue js?

I'd like to get access to vm data from outside the instance like so:
myComponent.vue
export default {
data() {
return {
name: 'Joe'
};
}
}
main.js
var vm = new Vue({
el: '#app',
render: h => h(myComponent)
});
Desired Result
console.log(vm.name); // should return - Joe
For some reason, console returns undefined. What I'm doing wrong?
To access vue.js object datas from inside you can use $property_name. Example
var vm = new Vue({
el: '#app',
data() {
return {
name: "Kapucni",
}
},
template: '<div>{{ name }}</div>'
});
// use $name .property
console.log(vm.$data.name);
console.log(vm.$el);
// calling functions from $method, etc ...
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
<div id='app'>
</div>
Thanks to comments from ittus, I realised that I have to look for child components, not the root component.
We can get to child component like so:
vm.$children[0].name
where $children[0] is a direct (and first in this case) child of the root component.

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.