VueJS - lIst of all vue instances on a page - vue.js

Quite simply, given something like the following code, with a mix of new Vue() instances and components, how can I list all Vue instances on a page, and what does that list look like?
<div id="instance1">
{{ name }}
</div>
<div id="instance2">
{{ name }}
</div>
<my-component id="some-element"></my-component>
Javascript:
new Vue({
el: '#instance1',
data: {
name: 'Sammy',
}
});
new Vue({
el: '#instance2',
data: {
name: 'Bobby',
}
});
Vue.component('my-component', {
data: function(){
return {
name: 'Mark',
}
},
template: '<div>Hello: {{ name }}</div>',
});

You cannot really do that. You will have to maintain the counter yourself. It means you will have to wrap every invocation to new Vue() like:
let counter = 0;
const rootVueComponents = [];
function makeRootInstance(el, data) {
const instance = new Vue(el, data);
rootVueComponents.push(instance);
counter++;
return instance;
}
Again, this will only provide you with the list of root Vue instances. If you have a component hierarchy, then it will not work.
And finally, if you really wish to have a list of all the components, then create a global created() mixin for all the Vue components and maintain this counting logic there.
Also, I wonder why you might need this. I don't see the real need to do this unless of course, you are experimenting.

For people who (like me) trying to find a way to check presence of any Vue instances on some page:
It can be done with this code:
Array.from(document.querySelectorAll("*")).filter(el => '__vue__' in el)
Each DOM element that has attached Vue instance must contain __vue__ property.

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>

Props passed to dynamic component in Vue are not reactive

I am using Vue's dynamic component to load a component depending on what is to be displayed. As these components all have different props I build an object and pass it in via v-bind depending on what I need to use from the original state model.
However, when I do this I lose the reactive nature of Vue's props data flow. The code sample below shows an example of this with the name changing on the standard component but not on the dynamic version.
I expect this is something to do with the string value being copied into the new object, rather than a reference to the original reactive property. Can anyone advise on how I can make this work as expected?
Vue.config.productionTip = false;
Vue.component("example-component", {
props: ["name"],
template: '<span style="color: green;">{{name}}</span>'
}
);
var app = new Vue({
el: "#app",
data: {
person: {
name: "William"
},
component: null
}
});
// Load the dynamic component
setTimeout(function() {
app.component = {
is: 'example-component',
props: { name: app.person.name }
}
// Change the name
setTimeout(function() {
app.person.name = "Sarah";
}, 2000);
}, 2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<strong>Standard Component</strong><br />
<example-component :name="person.name"></example-component><br /><br />
<div v-if="component">
<strong>Dynamic Component</strong><br />
<component :is="component.is" v-bind="component.props"></component>
</div>
</div>
There's two different objects here, person and component, which are declared in the app component's data property.
It is app.component.props.name which is bound to the component's prop, but you are modifying app.person.name which is separate and not bound to the component.
app.component.props and app.person are two separate object instances, and so modifying one of these object's properties will have no effect on the other.
It's difficult to suggest a suitable solution to your problem because your example is too simple (and a little contrived). What you want will not work so long as you are copying the name value between different objects.
I'd redo all of the code, and perhaps use a computed property instead. But with the least amount of changes you can do this:
app.component = {
is: 'example-component',
props: {
get name() { return app.person.name; }
}
}
Now app.component.props.name is actually a getter function which returns app.person.name. Vue can observe this, and will react when app.person.name changes.

How vue use getter setter on v-model?

<div id="app">
<input v-model="msg"/>
<p>{{ msg }}</p>
</div>
<script>
class A{
}
A.a = 1
new Vue({
el: '#app',
data: {
},
computed: {
msg: {
cache: false,
set: function(val){
A.a = val
},
get: function(){
return A.a
}
}
}
})
</script>
run on jsfiddle
How vue use getter setter on v-model? I tried use getter and setter on v-model, but it didn't work.
Your getters and setters are fine as is. (They're not strictly necessary in this example, since they're not doing anything to modify the user input, but I assume that's a simplification for the purposes of your question.)
There are two separate issues with your code:
the input field is outside the Vue root node, so the framework can't see it. [You corrected this in a late edit to the question.]
You're defining your data (A.a) outside of Vue, so the framework doesn't know to watch it for changes.
For the framework to be reactive to changes you must put the variable A in the data block of the component (and, if you really need an external variable, copy the updated value into it using the setter function).
new Vue({
el: '#app',
data: {
A: { a: 1 } // <-- your external variable, moved to where Vue can see it
},
computed: {
msg: {
set: function(val) {
this.A.a = val;
// If necessary, also copy val into an external variable here
},
get: function() {
return this.A.a
}
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<input v-model="msg" />
<p>{{ msg }}</p>
</div>
First of all, your input has to be inside the #app element. Yours is currently not even being watched by Vue instance.
<div id="app">
<input v-model="msg"/>
<p>{{ msg }}</p>
</div>
Also, your A.a = 1 doesn't do anything. If you console.log A's value you won't see a anywhere. Instantiate A and add a variable in it's constructor:
class A {
constructor(a) { this.a = a}
}
let myA = new A(0)
with Vue instance like this it will work:
new Vue({
el: '#app',
data: {
a: myA.a = 1
},
computed: {
msg: {
set: function(val) {
this.a = val
},
get: function() {
return this.a
}
}
}
})
However, I'd move class instantiation to data:
data() {
return {
a: new A(1).a
}
},
If you keep a outside of data your setter will work and update the value, but your getter will not since variables outside of Vue instance aren't being observed.
The code to implement a model in vue is simple as:
var v1 = new Vue({
el:'#vue1',
data:{
msg:'demo'
}
});
And the html as:
<div id='vue1'>
<input type='text' v-model='msg' />
<p>
{{msg}}
</p>
</div>
The first problem is the scope. Since in your Vue instance you are providing the element id as #app, all the vue related markup should be inside an element with id app, in your case the div.
Second, the way you save the data, once you use v-model directive, it directly observes the changes in your model and make changes to the dom accordingly. You do not need the getter and setter methods.
Lastly, what was the code about the class A??
Please look into the the javascript manuals because it is well outside the scope of this question to explain all of that part in detail.
Here is the updated fiddle

Vue v-once equivalent

Is there a way to tell Vue to call a method only once when used as an expression?
Here's my code:
<div v-for="i in a.b.c.items">
<div :id="foo(i.value)"></div>
</div>
The way it is now, the foo() method will be executed any time anything on the model changes, not only items. Is there something in Vue that I can tell to evaluate this only once?
like this: <div :id.only-once="foo(i.value)"
Unfortunately that's only possible for certain events, e.g. in this question here. What you may want to consider instead is a computed property where you compute all of these values and return the array. This resulting array will be cached by Vue and will not be reevaluated until your items array is modified (and modified in such a way that Vue will detect the change).
An example:
Vue Setup
<script>
new Vue({
el: '. . .',
data: {
a: {b: {c: {items: [. . .]}}}
},
methods: {
foo: function(val) {
. . .
}
},
computed: {
itemsAfterFoo: function() {
var this_vue_instance = this;
var computed_items = [];
this_vue_instance.items.forEach(function(item) {
computed_items.push(this_vue_instance.foo(item.value));
});
return computed_items;
}
}
});
</script>
Template
<div v-for="(i, index) in a.b.c.items">
<div :id="itemsAfterFoo[index]"></div>
</div>
Or something to that effect.
More information on computed properties here: https://v2.vuejs.org/v2/guide/computed.html

Vue.js passing events up to parents in components

I have a Vue app like this:
<div id="example">
<event-emitting-component #clicked="methodOnRootInstance"></event-emitting-component>
<event-emitting-component-parent></event-emitting-component-parent>
<div v-for="click in clicks">
{{ click }}
</div>
</div>
And here is the JS for it:
// Child
Vue.component('event-emitting-component', {
template: '<div class="event-emitting-component" #click="$emit(\'clicked\')">Event emitter</div>'
});
// Parent
Vue.component('event-emitting-component-parent', {
template: '<div class="event-emitting-component-parent">' +
'A custom parent!'+
'<event-emitting-component></event-emitting-component>' + // <-- Please note child component
'</div>'
});
// create a root instance
new Vue({
el: '#example',
data: {
clicks : []
},
methods : {
methodOnRootInstance : function(){
this.clicks.push('Element clicked');
}
}
})
If you want to play with it it is also here:
https://codepen.io/EightArmsHQ/pen/QgbwPG?editors=1010
When you click the top child component a click is registered on the root element. Perfect.
When the child component is nested inside a parent (the second component in the example), obviously I can't add a #clicked="methodOnRootInstance" as that method doesn't exist inside the component.
What is the best way to pass an event up through a number of nested components?
I've made a stripped back example here, but in reality some components are two or three levels deep. Is the answer (what I think it is) that inside the parent component I would have the following:
Vue.component('event-emitting-component-parent', {
template: '<div class="event-emitting-component-parent">' +
'A custom parent!'+
'<event-emitting-component #clicked="passClicked"></event-emitting-component>' + // <-- Please note child component
'</div>',
'methods': {
passClicked : function(){
this.$emit('clicked')
}
}
});
And then in the html template add the same:
<event-emitting-component-parent #clicked="methodOnRootInstance"></event-emitting-component-parent>
I know I can get it to work like this, however it doesn't seem very elegant. I've looked in the docs and there are functions such as sync although I don't think it's what I need, I'm struggling to find the correct approach.
Bonus question: can vuex help with stuff like this?
This is the type of problem vuex is designed to solve, however, before you consider adding an extra layer of complexity to your app, you may be able to get away with a simple global event bus, which is simply an empty Vue object to emit events onto, which can then be listened for by any component in your app, bypassing the parent-child chain:
const bus = new Vue({});
Vue.component('comp-1', {
template: `<div>Comp 1 <button #click="emitClick">Click</button></div>`,
methods: {
emitClick(){
bus.$emit('comp-1-click');
}
}
})
Vue.component('comp-2', {
template: `<div><comp-1></comp-1></div>`,
})
new Vue({
el: '#app',
created(){
bus.$on('comp-1-click',() => {
console.log('Comp 1 clicked');
});
}
})
Here's the JSFiddle: https://jsfiddle.net/oatLhzLp/