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.
Related
Consider that instead of literal component url I want to use variable in component loader for the component path (vue2 + httpVueLoader not as node CLI)
.... html page .....
const myapp = new Vue({
el: '#app',
components: {
'my-component': httpVueLoader(this.component_path)
},
data() {
return {
component_path: my-path
}
},
....
});
where my-path is populated from other JavaScript part on the page external to shown vue code but nevertheless visible to vue.
Is this doable? and how it is doable if it is doable?
If someone asks as to why I need this - the answer is simple: I have different paths in different implementations and want to reuse same code for the logic and do not want every time to change paths for each implementation manually because they can be only static literal path string.
wider question is: when component code part is evaluated in vue rendering life-time - before or after assigning main vue script data? In other words are components loaded first and then data or data is loaded first and then components get loaded.
I have a Component where each instance needs some data being passed onto it, that Component is used multiple times and all instances of the component should receive the same data/prop.
<my-component :someProp="someValue"></my-component>
<my-component :someProp="someValue"></my-component>
<my-component :someProp="someValue"></my-component>
<!-- ...and lots more... -->
This is my current method:
Which gets kind of redundant, how can I pre-populate this someProp for ALL instances of my component?
I tried Vue.extend() but cannot figure out what syntax it expects, the documentation is not clear enough on that part.
This is how I imagined it to work:
// App.vue
import MyComponent from './components/MyComponent'
const PreConfiguredComponent = Vue.extend(MyComponent, {
props: {
someProp: "someValue"
}
})
export default {
name: 'app',
data: () => ({}),
components: {
MyComponent: PreConfiguredComponent
}
}
You get the idea, I don't know how to express it better. Doesn't have to be done with props but I don't know other methods to pass data along.
There are quite a lot of different ways to approach this. I doubt this will be anything like an exhaustive list but I can give some sense of the possibilities.
1. Store the value globally
The obvious choice here is the Vuex store. Just put the relevant data in the store and then the components can grab what they need.
However, a global store doesn't have to be Vuex. If you have no other reason to introduce Vuex then you might prefer something more ad hoc.
An alternative to the store is to hold the data on $root, an approach described in the official documentation:
https://v2.vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
A further alternative is just to have a .js file that can hold the data as a singleton. There are various ways to do it but in its simplest form it might be something like this:
export default {
myData: null
}
You'd then import that object and read/write the value of myData as required. It won't necessarily be reactive but I'm assuming that the initial value would be set at the start, before the application is created, and wouldn't change after that.
2. Store the value on the Vue prototype
This is quite similar to the above but I think it warrants a separate mention. It'd look something like this:
Vue.prototype.$myComponentData = 'someValue'
Then within any component you could access the value via the property $myComponentData.
Documentation: https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html
3. Provide/Inject
The provide/inject mechanism is one of the lesser known Vue features but it provides an alternative to using props to pass data down to child components. It has various pros and cons and typically you'd try to use props instead.
https://v2.vuejs.org/v2/api/#provide-inject
In short, it allows a component to provide a named value to all of its descendants without needing to explicitly pass it to each one individually. The descendant components can then choose which named properties they would like to have injected.
You wouldn't be able to use provide/inject to pass different values to descendant components but in your case the value is the same so it should work.
If you think this approach might be for you then I suggest some further reading:
https://blog.logrocket.com/how-to-make-provide-inject-reactive/
4. Refactor to remove the duplication
While this is unlikely to be the solution you go with I do think it is worth mentioning.
The starting premise for the question seems to be that passing the prop explicitly is a form of duplication. Extra noise and extra effort with the potential to make mistakes.
Potentially we can remove that duplication while still passing the prop. The key here is to refactor the template so that the child component only features once.
Obviously that would need to be within a loop so that we still get all the desired components. That loop would need a suitable data structure to drive it so that all the right components get created.
I assume the template in the question is a simplification. If you really do have several instances consecutively then refactoring to use a v-for should be pretty trivial. If, as seems more likely, the components are nested in various places within the layout then it can get unwieldy trying to encode that in data structures just to avoid a bit of template duplication.
Hopefully the idea is clear but if not you could give this a read:
https://michaelnthiessen.com/reducing-redundant-repetition
5. Dynamic components
This is what's alluded to in the question. There are various ways to do it but the core of the idea is that we change our component definitions at runtime. That doesn't necessarily mean creating a new component, it could equally mean prodding something into an existing component definition. We've already seen a variation of this idea with the Vue.prototype approach mentioned earlier.
In theory it could be done with the default value of a prop but it seems unnecessary to use a prop unless that prop is going to be set from the outside in the usual way in some cases.
We could set it as a property using data but personally I would be tempted to use a variation of the Vue.prototype trick to add the property to the component's own prototype:
MyComponent = Vue.extend({
template: `<div>{{ value }}</div>`
})
MyComponent.prototype.value = 'some value'
new Vue({
el: '#app',
components: {
MyComponent
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<my-component></my-component>
</div>
Using the prototype is relatively cheap from a performance perspective. There are potential problems with reactivity but they would only apply if the value can change, which it can't in this case.
Just to illustrate the prop and data approaches explicitly:
MyComponent = {
template: `<div>{{ value }}</div>`
}
PreConfiguredComponent = Vue.extend({
extends: MyComponent,
props: {
value: {
default: 'some prop value'
}
}
})
new Vue({
el: '#app',
components: {
MyComponent: PreConfiguredComponent
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<my-component></my-component>
</div>
and:
MyComponent = {
template: `<div>{{ value }}</div>`
}
PreConfiguredComponent = Vue.extend({
extends: MyComponent,
data () {
return {
value: 'some data value'
}
}
})
new Vue({
el: '#app',
components: {
MyComponent: PreConfiguredComponent
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<my-component></my-component>
</div>
You could equally try to tweak the imported MyComponent directly rather than extending it.
Following this tutorial, I'm trying to programmatically create instances of a component on my page.
The main snippet is this:
import Button from 'Button.vue'
import Vue from 'vue'
var ComponentClass = Vue.extend(Button)
var instance = new ComponentClass()
instance.$mount()
this.$refs.container.appendChild(instance.$el)
However I get two errors:
The component I'm trying to instantiate contains references to the store, and these don't work: "TypeError: Cannot read property 'state' of undefined".
For the last line of the snippet (this.$refs.container.appendChild(instance.$el)) I get this error: "Uncaught TypeError: Cannot read property 'container' of undefined"
I'm really not sure how to troubleshoot this, if anyone strong in Vue.js could give me some hint as to why I'm getting these errors and to solve them that would be terrific.
1) Since you're manually instantiating that component and it doesn't belong to your main app's component tree, the store won't be automatically injected into it from your root component. You'll have to manually provide the store to the constructor when you instantiate the component ..
import ProjectRow from "./ProjectRow.vue";
import Vue from "vue";
import store from "../store";
let ProjectRowClass = Vue.extend(ProjectRow);
let ProjectRowInstance = new ProjectRowClass({ store });
2) In a Vue Single File Component (SFC), outside of the default export this doesn't refer to the Vue instance, so you don't have access to $refs or any other Vue instance property/method. To gain access to the Vue instance you'll need to move this line this.$refs.container.appendChild(instance.$el) somewhere inside the default export, for example in the mounted hook or inside one of your methods.
See this CodeSandbox for an example of how you may go about this.
This is another way to instantiate a component in Vue.js, you can use two different root elements.
// Instantiate you main app
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
//
// Then instantiate your component dynamically
//
// Create a component or import it.
const Hello = {
props: ['text'],
template: '<div class="hello">{{ text }}</div>',
};
// Create a componentClass by Vue.
const HelloCtor = Vue.extend(Hello);
// Use componentClass to instantiate your component.
const vm = new HelloCtor({
propsData: {
text: 'HI :)'
}
})
// then mount it to an element.
.$mount('#mount');
It works by assigning "this" to the property "parent". By setting the parent you also have access to the $store in the new instance. (Provided that "this" is another Vue instance/Component and already has access to the store, of course)
new (Vue.extend(YourNewComponent))({
parent: this,
propsData: {
whatever: 'some value',
},
}).$mount(el.querySelector('.some-id'))
If you don't need the reference to the parent, you can just leave "parent: this," out.
Important note: When mounting many (like 500+) items on the page this way you will get a huge performance hit. It is better to only give the new Component the necessary stuff via props instead of giving it the entire "this" object.
I went down this path, following all the examples above, and even this one: https://css-tricks.com/creating-vue-js-component-instances-programmatically/
While I got far, and it works (I made a lot of components this way), at least for my case, it came with drawbacks. For example I'm using Vuetify at the same time, and the dynamically added components didn't belong to the outer form, which meant that while local (per component) validation worked, the form didn't receive the overall status. Another thing that did not work was to disable the form. With more work, passing the form as parent property, some of that got working, but what about removing components. That didn't go well. While they were invisible, they were not really removed (memory leak).
So I changed to use render functions. It is actually much easier, well documented (both Vue 2 and Vue 3), and everything just works. I also had good help from this project: https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/
Basically, to add a function dynamically, just implement the render() function instead of using a template. Works a bit like React. You can implement any logic in here to choose the tag, the options, everything. Just return that, and Vue will build the shadow-DOM and keep the real DOM up to date.
The methods in here seems to manipulate the DOM directly, which I'm glad I no longer have to do.
I'm learning Vue.JS and all my attempts to reference the dashboards property from the currentDashboard data expression result in 'dashboards is not defined'. Is Vue somehow evaluating the currentDashboard expression prior to dashboards or do I need some qualifier to reference it, this does not help?
vue = new Vue({
el: '#dashboard',
data: {
dashboards: store.getDashboards(),
currentDashboard: dashboards[0],
}
})
I think that one solution is that you could use the computed method for it, because dashboards[0] are not defined in the same created cycle. Try with something like:
data: {
dashboards: store.getDashboards(),
},
computed: {
currentDashboard: function () { return this.dashboards[0] }
}
This way the variable is defined when you make the currentDashboard call and you don't have to refactor the Vue.js call for this var.
Edit:
Yes, if you want to know why, as points Joel, in the official documentation you can read:
If you know you’ll need a property later, but it starts out empty or
non-existent, you’ll need to set some initial value.
In this case, the data() method starts with all the values in a queue, without assigning it, and for this, the starting value is undefined.
Here: https://v2.vuejs.org/v2/guide/instance.html
Hope it helps!
you can create dashboard variable before you return data properties so that you can use it multiple times in data properties.
vue = new Vue({
el: '#dashboard',
data() {
let dashboard = store.getDashboards()
dashboards: dashboard,
currentDashboard: dashboard[0],
}
I hope this is helpful
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