Nested Vue instances - vue.js

Is there anyway to have nested Vue instances? I know about Vue components and I use them extensively in my applications but in this use case I have different applications (I mean different projects) that are loading inside each other in a page.
For example when I do something like the following:
<div id="parent">
{{msg}}
<div id="child">
{{msg}}
</div>
</div>
...
new Vue({
el: '#parent',
data: { msg: 'sth' },
})
new Vue({
el: '#child',
data: { msg: 'sth else' },
})
But both msg's uses msg data of parent Vue instance. Here I just want to show an example but in my use case these instances are not next to each other and just somehow relate to each other through Django framework (which is not important to notice here).
Update
It's not a duplicate question. Person who asked that question doesn't need nested Vue instances and just needs components. But I explicitly said that I know about components but need nested Vue instances.
Issue
According to this issue, they are not going to implement such behavior.

If you really need to have nested instances, use VueJS v-pre directive. You can add v-pre to the child app. More details about it here.
<div id="parent">
{{msg}}
<div id="child" v-pre>
{{msg}}
</div>
</div>
...
new Vue({
el: '#parent',
data: { msg: 'sth' },
})
new Vue({
el: '#child',
data: { msg: 'sth else' },
})
{{ msg }} for child instance will be "sth else". Note that the nested instance (#child element) is not compiled by vue parent instance because of the v-pre directive.

Related

vue instance with child vue instance possible or alternative approach?

I would like to develop a vuejs multitouch app for a 4K display. It’s about 3-4 cards that are on a background and actually show the same content. For each of the cards a different entry page is visible.
Is it possible to pack several other instances (with the same content) of vuejs in divs within a Vue instance?
Somehow I would like to integrate an instance with store and router multiple times, but I can’t figure it out.
It would be helpful if someone can help me here, maybe provide a link or an approach.
I am looking for an approach how I can display the same content 3 times at the same time, at best with routes and nested routes. Each User can navigate separately, everyone has their own history via GUI.
when I try to use 2 instance inside the main vue instance 3 different routers, it’s always renders the content of main route.
I found this example where to instances are side by side, works great: https://jsfiddle.net/m91e7s2v/
but not inside a parent instance? why?
inside app.vue
<div id="app">
<VueToolMultitouch class="schatten" :startX="100" :startY="100" :startColor='"#00FF00"' id="id1" :idName="'id1'" :startZ="2">
<div id="subapp1">
<router-link to="/">/home</router-link>
<router-link to="/foo">/foo</router-link>
<p>Route path: {{ $route.path }}</p>
<router-view></router-view>
</div>
<h2>Passing Text 1</h2>
</VueToolMultitouch>
<VueToolMultitouch class="schatten" :startX="200" :startY="600" :startColor='"#FF0000"' id="id2" :idName="'id2'" :startZ="3">
<div id="subapp2">
<router-link to="/">/home</router-link>
<router-link to="/foo">/foo</router-link>
<p>Route path: {{ $route.path }}</p>
<router-view></router-view>
</div>
<h2>Passing Text 2</h2>
</VueToolMultitouch>
</div>
inside main.js
import router1 from "./router/router";
import router1 from "./router/router-1";
import router2 from "./router/router-2";
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
new Vue({
router: router1,
}).$mount("#subapp1");
new Vue({
router: router2,
}).$mount("#subapp2");
An alternative would be if everything is implemented with a single vue instance, but each of the cards gets its own "router".
maybe someone has an idea what that might look like.
The problem is that every child gets bound to the parent vue app and its prototype, this overrides the router of the children. I think that you'll need either to use iframes for the children or make the parent app handle with state the children views.
Edit:
I just learned about v-pre, this directive prevents Vue from "compiling" an HTML node and it's children.
You can basically have as many Vue instances even if they're nested as long as you put v-pre on the tag you use to mount the child Vue app.
Here's a working fiddle https://jsfiddle.net/dja36s7x/18/
I found an alternative way in the VueJS forum.
<div id="app">
<div class="row">
<my-child1></my-child1>
<my-child2></my-child2>
</div>
<div class="row">
<my-child3></my-child3>
<my-child4></my-child4>
</div>
</div>
const routes = [
{
path: '/page1',
component: { template: '<p>Page 1</p>' }
}, {
path: '/page2',
component: { template: '<p>Page 2</p>' }
}, {
path: '/page3',
component: { template: '<p>Page 3</p>' }
}
]
const MyChild = {
template: `
<div>
<router-link to="/page1">Page 1</router-link>
<router-link to="/page2">Page 2</router-link>
<router-link to="/page3">Page 3</router-link>
<button #click="$router.back()">Back</button>
<div>{{ $route.path }}</div>
<router-view />
</div>
`
}
function getChild() {
return {
extends: MyChild,
router: new VueRouter({
mode: 'abstract',
routes
})
}
}
new Vue({
components: {
MyChild1: getChild(),
MyChild2: getChild(),
MyChild3: getChild(),
MyChild4: getChild()
}
}).$mount('#app')
JSFiddle Example
Here, the components are expanded with their own router.
I currently no longer need the route via nested instances. but i will test the v-pre on everyone.
It seems this might be achieved using a hierarchy of components. If you're sure you need different Vue app instances, then it's worth going with Vue 3 as it's abandoned the idea of a shared global config, allowing you to create many Vue instances with createApp. All with different configurations.
You could do something like this (JS Fiddle here):
Vue.createApp({
name: 'App',
template: `
<h1>Primary App</h1>
<div id="subAppOne"></div>
<div id="subAppTwo"></div>
<div id="subAppThree"></div>
`
}).mount('#app');
Vue.createApp({
name: 'AppOne',
template: `<h2>App One</h2>`,
}).mount('#subAppOne');
Vue.createApp({
name: 'AppTwo',
template: `<h2>App Two</h2>`,
}).mount('#subAppTwo');
Vue.createApp({
name: 'App Three',
template: `<h2>App Three</h2>`,
}).mount('#subAppThree');
You can specify different routers with .use() on each app instance, just before calling mount().
const routerOne = VueRouter.createRouter({
history: VueRouter.createWebHistory(),
routes: [/* … */],
});
Vue.createApp({/* … */}).use(routerOne).mount('#appOne');

VueJs: bind `v-on` on a custom component to replace an existing one

In order to ease the styling of my page, I'd like to create a bunch of mini components like, and exploit how attributes are merged in VueJs. So for example, here is a minimal js file also hosted on this JSFiddle:
Vue.component('my-button', {
template: '<button style="font-size:20pt;"><slot></slot></button>'
})
var app = new Vue({
el: "#app",
data: {
message: "world",
},
methods: {
sayHello: function () {
alert("Hello");
}
}
})
and then in my html I just want to use <my-button> instead of button:
<div id="app">
Hello {{message}} <my-button #click="sayHello" style="color:red;">Style works, but not click</my-button> <button v-on:click="sayHello" style="color:red;">Both works</button>
</div>
Unfortunately, it seems that attributes are merged, but not listeners, so it means that I can't do v-on:click on my new button... Any way to make it possible?
Thanks!
-- EDIT --
I saw the proposition of Boussadjra Brahim of using .native, and it works, but then I found this link that explains why it's not a great practice and how to use v-on="$listeners" to map all listeners to a specific sub-button. However, I tried, to just change my template with:
template: `<button style="font-size:20pt;" v-on="$listeners"><slot></slot></button>`,
but I get an error:
Vue warn: Property or method "$listeners" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option."
Here is the JSFiddle.
Your fiddle didn't work because you were using an old version of Vue, $listeners was added in Vue 2.4.0.
Here's a demo:
Vue.component('my-button', {
template: '<button style="color: red" v-on="$listeners"><slot/></button>'
})
new Vue({
el: '#app',
methods: {
sayHello() {
alert('Hello')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-button #click="sayHello">Custom Button</my-button>
<button #click="sayHello">Ordinary Button</button>
</div>

Vue data vue object variables from component

I am very new to Vue and I am having difficulty accessing data in main Vue instance from component. In the Vue instance I have:
var vm = new Vue({
computed: {},
data: {
show: true
},
and in the component, I want to do something like:
<button v-show="vm.show" #click="setDefaults(styleguide)">Set Default</button>
My goal is when 'show' value changes, I want to display/hide the button. It is little difficult/weird because I create template in the component, not in the html. When I try this code, it doesn't understand 'vm.show'. I feel like I need to create data in the component and tie the data to the 'show' variable, or create computed in the component or something (I believe computed is like watcher?). If there is easy way to handle this, please help.
I'm also new to VueJs, but I believe the issue is you haven't provided the el argument to the Vue instance, and in this case assigning the Vue instance to a variable doesn't do anything.
I think you want something like
<div id="app">
<button v-show="show" #click="setDefaults(styleguide)">Set Default</button>
</div>
<script>
new Vue({
el: '#app',
computed: {},
data: {
show: true
},
...
);
</script>
So I guess my question wasn't very clear, but I got it to figure it out. In the component code, I needed to add:
Vue.component('styleguide', {
computed: {
show: function () {
return vm.show
}
},
props: ['styleguide'],
template: `
<div>
<div>
<p>
<button v-show="show" #click="setDefaults(styleguide)">Set Default</button>
This allows me to access 'show' in the main Vue instance from the component template. Whenever other component modifies 'show' variable in the main Vue instance, the button disappears/reappears. I am not sure if this makes sense, but this is how I got it to work.
Two things, in the template all variables are already scoped from the component so you don't need the vm. in there. The second thing is that the data property of a component expects a function which returns an object.
var vm = new Vue({
computed: {},
data: () => ({
show: true
}),
<button v-show="show" #click="setDefaults(styleguide)">Set Default</button>
If you need to access data from a parent component you will need to pass it on using props. you can also try to do it using provide/inject if that suits your usecase better

How do I use a vue component without instantiating a root instance?

Details
I am working on a website with different page setups.
My setup is not an SPA and so I do not have the privalige of one single root instance.
Problem
This means that if I create a component I have to register a root vue instance every time I want to use my component.
Example of the issue
I create my custom component as a global component:
Vue.component('mycomponent', { /* options */ });
According to the vue docs I have to register a root instance in order to use my component
new Vue({ el: '#root-instance' });
<div class="header" id="root-instance">
<mycomponent></mycomponent>
</div>
Then in a different section I want to use the same component but I have to create another root instance:
new Vue({ el: '#other-root-instance' });
<div class="sidebar" id="other-root-instance">
<mycomponent></mycomponent>
</div>
I tried using a class for instantiating, something like:
new Vue({ el: '.root-instance' });
But view only loads this once.
Question
Is there any way to load a component but not instantiate a root instance every time I use it?
Note: I have several root instances on the page and therefore can not declare a single root instance for the page. Effectively I do not want to make my page a Single Page App.
You don't have to wrap your component in a root instance div, you can make the component tag the root instance.
Vue.component('myComponent', {
props: ['custom'],
template: '<div>Hi there, I am the {{custom}}</div>'
});
new Vue({
el: '#sidebar'
});
new Vue({
el: '#topmenu'
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.4/vue.min.js"></script>
<my-component id="sidebar" custom="sidebar"></my-component>
<div>
Some stuff that is not under Vue control {{custom}}
</div>
<my-component id="topmenu" custom="top menu"></my-component>

Vue.js 2.0 Dynamic Props Docs

js 2.0 and I'm stock in dynamic props.
See Image attached
My HTML code like this:
<div id="app">
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
</div>
My Component code:
Vue.component('child', {
props: ['myMessage'],
template: '<p>{{ myMessage }}</p>',
})
var app = new Vue({
el: "#app",
});
I know that data should be a function but how I'm going to implement it. I get this error on the console.
Property or method "parentMsg" is not defined on the instance but referenced during render
I think message is clear. "parentMsg" is not defined on the instance. You have to define parentMsg at parent level. like following:
var app = new Vue({
data: {
"parentMsg": ""
}
el: "#app"
});
You can have a working fiddle here.