What's the difference between this code:
new Vue({
data () {
return {
text: 'Hello, World'
};
}
}).$mount('#app')
and this one:
new Vue({
el: '#app',
data () {
return {
text: 'Hello, World'
};
}
})
I mean what's the benefit in using .$mount() instead of el or vice versa?
$mount allows you to explicitly mount the Vue instance when you need to. This means that you can delay the mounting of your vue instance until a particular element exists in your page or some async process has finished, which can be particularly useful when adding vue to legacy apps which inject elements into the DOM, I've also used this frequently in testing (See Here) when I've wanted to use the same vue instance across multiple tests:
// Create the vue instance but don't mount it
const vm = new Vue({
template: '<div>I\'m mounted</div>',
created(){
console.log('Created');
},
mounted(){
console.log('Mounted');
}
});
// Some async task that creates a new element on the page which we can mount our instance to.
setTimeout(() => {
// Inject Div into DOM
var div = document.createElement('div');
div.id = 'async-div';
document.body.appendChild(div);
vm.$mount('#async-div');
},1000)
Here's the JSFiddle: https://jsfiddle.net/79206osr/
According to the Vue.js API docs on vm.$mount(), the two are functionally the same, except that $mount can (optionally) be called without an element selector, which causes the Vue model to be rendered separate from the document (so it can be appended later). This example is from the docs:
var MyComponent = Vue.extend({
template: '<div>Hello!</div>'
})
// create and mount to #app (will replace #app)
new MyComponent().$mount('#app')
// the above is the same as:
new MyComponent({ el: '#app' })
// or, render off-document and append afterwards:
var component = new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)
In the example you provide, I don't believe there is really much difference or benefit. However, in other situations there may be a benefit. (I have never encountered situations like the following).
With $mount() you have more flexibility what element it will be
mounted on if that were to ever be necessary.
Similarly you if for some reason you need to instantiate the
instance before you actually know what element it will be mounted on
(maybe an element that is created dynamically) then you could mount
it later using vm.$mount()
Following along with the above you could use also use mount when you
need to make a decision before hand which element to mount to
assuming that there may be two or more possibilities.
Something like...
if(document.getElementById('some-element') != null){
// perform mount here
}
Top answer is good enough. just left a comment here as I don't have enough reputation points. Alternativley:
setTimeout(() => {
const element = document.createElement('div');
document.body.appendChild(element);
vm.$mount(element);
}, 0)
Besides all the great answers here that say using $mount is better, another thing I would like to add is about the correctness of your code for when it gets evaluated by clean-code-tools (like SonarQube etc).
In short, the code is cleaner when using $mount separately. Because otherwise the clean-code-tools complains:
"Objects should not be created to be dropped immediately without being used"
So in this example the clean-code-tool complains:
new Vue({
el: '#some-id',
...
})
The solution is:
let instance = new Vue({
...
})
instance.$mount('#some-id')
And so everybody is happy again
Related
I created the child using:
const ComponentClass = Vue.extend(someComponent);
const instance = new ComponentClass({
propsData: { prop: this.value }
})
instance.$mount();
this.$refs.container.appendChild(instance.$el);
When this.value is updated in the parent, its value doesn't change in the child. I've tried to watch it but it didn't work.
Update:
There's an easier way to achieve this:
create a <div>
append it to your $refs.container
create a new Vue instance and .$mount() it in the div
set the div instance's data to whatever you want to bind dynamically, getting values from the parent
provide the props to the mounted component from the div's data, through render function
methods: {
addComponent() {
const div = document.createElement("div");
this.$refs.container.appendChild(div);
new Vue({
components: { Test },
data: this.$data,
render: h => h("test", {
props: {
message: this.msg
}
})
}).$mount(div);
}
}
Important note: this in this.$data refers the parent (the component which has the addComponent method), while this inside render refers new Vue()'s instance. So, the chain of reactivity is: parent.$data > new Vue().$data > new Vue().render => Test.props. I had numerous attempts at bypassing the new Vue() step and passing a Test component directly, but haven't found a way yet. I'm pretty sure it's possible, though, although the solution above achieves it in practice, because the <div> in which new Vue() renders gets replaced by its template, which is the Test component. So, in practice, Test is a direct ancestor of $refs.container. But in reality, it passes through an extra instance of Vue, used for binding.
Obviously, if you don't want to add a new child component to the container each time the method is called, you can ditch the div placeholder and simply .$mount(this.$refs.container), but by doing so you will replace the existing child each subsequent time you call the method.
See it working here: https://codesandbox.io/s/nifty-dhawan-9ed2l?file=/src/components/HelloWorld.vue
However, unlike the method below, you can't override data props of the child with values from parent dynamically. But, if you think about it, that's the way data should work, so just use props for whatever you want bound.
Initial answer:
Here's a function I've used over multiple projects, mostly for creating programmatic components for mapbox popups and markers, but also useful for creating components without actually adding them to DOM, for various purposes.
import Vue from "vue";
// import store from "./store";
export function addProgrammaticComponent(parent, component, dataFn, componentOptions) {
const ComponentClass = Vue.extend(component);
const initData = dataFn() || {};
const data = {};
const propsData = {};
const propKeys = Object.keys(ComponentClass.options.props || {});
Object.keys(initData).forEach(key => {
if (propKeys.includes(key)) {
propsData[key] = initData[key];
} else {
data[key] = initData[key];
}
});
const instance = new ComponentClass({
// store,
data,
propsData,
...componentOptions
});
instance.$mount(document.createElement("div"));
const dataSetter = data => {
Object.keys(data).forEach(key => {
instance[key] = data[key];
});
};
const unwatch = parent.$watch(dataFn || {}, dataSetter);
return {
instance,
update: () => dataSetter(dataFn ? dataFn() : {}),
dispose: () => {
unwatch();
instance.$destroy();
}
};
}
componentOptions is to provide any custom (one-off) functionality to the new instance (i.e.: mounted(), watchers, computed, store, you name it...).
I've set up a demo here: https://codesandbox.io/s/gifted-mestorf-297xx?file=/src/components/HelloWorld.vue
Notice I'm not doing the appendChild in the function purposefully, as in some cases I want to use the instance without adding it to DOM. The regular usage is:
const component = addProgrammaticComponent(this, SomeComponent, dataFn);
this.$el.appendChild(component.instance.$el);
Depending on what your dynamic component does, you might want to call .dispose() on it in parent's beforeDestroy(). If you don't, beforeDestroy() on child never gets called.
Probably the coolest part about it all is you don't actually need to append the child to the parent's DOM (it can be placed anywhere in DOM and the child will still respond to any changes of the parent, like it would if it was an actual descendant). Their "link" is programmatic, through dataFn.
Obviously, this opens the door to a bunch of potential problems, especially around destroying the parent without destroying the child. So you need be very careful and thorough about this type of cleanup. You either register each dynamic component into a property of the parent and .dispose() all of them in the parent's beforeDestroy() or give them a particular selector and sweep the entire DOM clean before destroying the parent.
Another interesting note is that in Vue 3 all of the above will no longer be necessary, as most of the core Vue functionality (reactivity, computed, hooks, listeners) is now exposed and reusable as is, so you won't have to $mount a component in order to have access to its "magic".
I'm going through Vue.js v2 documentation right now, and feel like I am running into a conflict in how data is structured. In official documentation they use the following to create data:
var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data
})
But while watching a video on Vue JS Crash Course 2019 the speaker uses the following approach:
export default {
name: "app",
data() {
return {
data: data
}
}
}
Are both these approaches valid as of today for Vue.js in 2019? If so, when do we use them in what environments?
Also, why is it with the second example, I can't assign it to a specific element ID, like in the first example?
There is a lot of talk in the documentation about using new Vue but I have not seen this used wi thin an export default tutorial. Can this be done? for the purpose of using javascript to manipluate objects? for example:
new Vue({
data: {
a: 1
},
created: function () {
// `this` points to the vm instance
console.log('a is: ' + this.a)
}
})
// => "a is: 1"
Thanks for walking through this with me so I get an overall understanding on when to use either approach.
You use new Vue() in JavaScript files and export default {…} in Vue files, with the .vue extension. A build tool will convert the Vue file to JavaScript and the code converted to Vue components.
The Vue single component file is generally used, but you can use the Vue() syntax if you don't want to use build tools.
In a Vue file, you're creating a single component. If you want to do manipulations, you should do it inside the component, there's no need to assign the component to a variable.
You can use new Vue() and Vue files together, but I think it's better to stick to one way of thinking. I recommend using Vue files with the export default {…} syntax.
I have an PHP var used in a blade template and want to pass it to a vue's method.
I'm still learning so sorry if it seems obvious but I read the docs but found noting useful.
So I have this piece of code in my HTML
<chat-messages :messages="messages" :surgery_id="{{ $surgery->id }}"></chat-messages>
And in my JS
Vue.component('chat-messages', require('./components/ChatMessages.vue'));
const app = new Vue({
el: '#chat',
methods: {
fetchMessages() {
axios.get('/messages/').then(response => {
this.messages = response.data;
});
},
}
});
And I want to use something like axios.get('/messages/' + surgery_id).then(...)
But I can't figure out how to retrieve this surgery_id variable
In my ChatMessages.vue, I well created the properties
<template>
//Stuff to loop & display
</template>
<script>
export default {
props: ['messages' , 'surgery_id']
};
</script>
Use this as you do normally with the data:
axios.get('/messages/' + this.surgery_id).then(...)
You can access all the property of data option,props, and methods using this as context.
Further, if you want to use ES6, then it's even easier without concatenating them: (using tilde key `)
axios.get(`/messages/${this.surgery_id}`).then(...)
As per your query, you also need to pass props in your instance:
const app = new Vue({
// ...
propsData:{
surgery_id: 'your id value'
}
See my another post for more help.
I am reading up on Vue components, and find their explanation of why data needs to be a function somewhat confusing:
The root instance
var vm = new Vue({
el: '#example',
data: {
message: 'here data is a property'
}
})
A component
var vm = new Vue({
el: '#example',
data: function () {
return {
counter: 0
}
}
})
The Vue docs explain this difference by assigning a global counter variable to each component, and then they act surprised that each component shares that same data... Also they don't explain why they already use a function here.
var data = { counter: 0 }
Vue.component('simple-counter', {
template: '<div>{{ counter }}</div >',
data: function () {
return data
}
})
Of course the data is shared now
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
When you reference a global object as your data source, it's no surprise that the components don't have their own data. That is also true for root Vue instances that have data as a property.
var mydata = { counter: 0 }
var vm1 = new Vue({
el: '#example1',
data: mydata
})
var vm2 = new Vue({
el: '#example2',
data: mydata
})
So I'm still left with the question why a component can't have a data property?
From my understanding of this, It's to save memory
Many frameworks, such as Angular 2 or, (at times) React, make each instance of a component a separate object. This means that everything each component needs is initialized for every component. Normally though, you really only need to keep a component’s data separate for each initialization. Methods and such stay the same.
Vue avoids that pitfall by having data be a function that returns an object. That allows separate components to have separate internal state without needing to fully re-instantiate the entire component. Methods, computed property definitions, and lifecycle hooks are created and stored only once, and run against every instance of a component.
See this
The data option should always be a function in the context of components which returns a fresh object.
This precaution is made by vue. So whenever you define the object directly in the data option, vue will catch for making the mistake.
Components are never allowed to directly mutate its state. This prevents us from messing up and doing bad things where components do not have their own state.
If this precaution is not made by vue, then you will have a chance to mutate any other components that owns from the component and this would be a security issue.
Example from the documentation:
It’s good to understand why the rules exist though, so let’s cheat.
<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>
var data = { counter: 0 }
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
// data is technically a function, so Vue won't
// complain, but we return the same object
// reference for each component instance
data: function () {
return data
}
})
new Vue({
el: '#example-2'
})
Since all three component instances share the same data object, incrementing one counter increments them all! Ouch. Let’s fix this by instead returning a fresh data object:
data: function () {
return {
counter: 0
}
}
Now all our counters each have their own internal state.
It must be a function because otherwhise the data will be shared among all instances of the component, as objects are call by reference rather than call by value. This does not only happen when you reference a global object but also when data is an object itself.
If data is a factory-function that returns an object this object will be created from scratch every time you mount a new instance of the component instead of just passing a reference to the global data.
Because when Vue init data,
function initData(vm){
let data = vm.$options.data
data = vm._data = typeof data === ‘function’ ? getData(data, vm) : data || {}
/*
Because here,data is a reference from vm.$options.data,
if data is an object,
when there are many instances of this Component,
they all use the same `data`
if data is a function, Vue will use method getData( a wrapper to executing data function, adds some error handling)
and return a new object, this object just belongs to current vm you are initializing
*/
……
// observing data
observe(data, true)
}
why Vue forces the data property to be a function is that each instance of a component should have its own data object. If we don’t do that, all instances will be sharing the same object and every time we change something, it will be reflected in all instances.
var vm = new Vue({
el: '#example',
**data: function () {
return {
counter: 0
}**
}
I need to find a way to create dynamic component instances programmatically (instead of through a template).
Although I have found a solution (it seems), the naive way of just create a new vue-instance using new Vue(..) results in an error:
Failed to mount component: template or render function not defined.
This seems strange to me, since it should just be possible to do so afaik. I.e.: there's a template defined on the extended vue class.
Please see the code below, with 2 commented out ways that work, and the current (non commented out) code that doesn't work.
My contrived code:
const HelloComponent = Vue.extend({
template: '<p>Welcome home!</p>'
});
const Home1 = {
template: '<p>Welcome home!</p>'
};
const Home2 = {
extends: HelloComponent
};
new Vue({
el: '#app',
data() {
return {
// currentView: Home1 // does work
// currentView: Home2 // does work
currentView: new HelloComponent() //does NOT work. Why?
};
},
template: '<component :is="currentView"></component>',
});
The built in component component can accept a string, a component constructor, or a component definition object. Vue.extends creates a new component constructor function. To make your code work you should pass the constructor function itself, not the result of creating it.
currentView: HelloComponent
Essentially, the result of new HelloComponent() is not a component definition object or a component constructor function, it's a Vue instance.