Why computed property in vue.js? - vue.js

I am a beginner to vue.js. What is the reason to use computed property instead of methods. Why am asking this question is, because the both computed property and methods do the same thing

Methods can get attributes and need to be called manually, computed is not.
Also you no need to clone code while multiple using. Think about computed properties like a shortcut to you additional logic.
Putting too much logic in your templates can make them bloated and hard to maintain. For example:
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
The same, using computed property:
<div id="example">
{{ reversedMessage }}
</div>
Vue code:
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// a computed getter
reversedMessage: function () {
// `this` points to the vm instance
return this.message.split('').reverse().join('')
}
}
})
Difference
But main difference as i think, is caching. When you call method 5 times, you get 5 computings. On the other side a computed property is computing only once (when changing), and then return a cached value.

Related

Set Component prop for all instances / Pass same data to all Component instances

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.

Vue: How to perform reactive object change detection in v-for?

I read this documentation but cannot use the proposed solution.
I have a v-for loop over objects. These objects are changed dynamically over time and I need that change to show reactively in the v-for loop.
<b-row lg="12" v-for="data in objects" :key="data.id">
<div v-if="data.loading">
loading...
{{data.loading}}
</div>
<div v-else>
loaded, done
{{data.loading}}
</div>
</b-row>
In my methods, I have a for loop that downloads data for each object and changes the object value like this:
for(var i = 0; i<response.ids.length; i++){
var newId = response.ids[i].id
this.objects.newId = {"loading":true, "id": newId}
downloadSomething(newId).then(res => {
this.objects.newId = res[0] //<-- this change needs to be shown reactively.
})
}
According to Vue documentation, Object changes are not reactive:
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` is now reactive
vm.b = 2
// `vm.b` is NOT reactive
Vue propses some workaround like this:
Vue.set(vm.userProfile, 'age', 27)
UPDATE
But for my case, this just creates a new parameter in the object with the same ID and creates a duplicate key warning and other problems.
I also tried Vue.delete just before Vue.set but it is not actually deleting.
Is there a way to not replace the key/value pair but add more/change parameters to the first child of the root with the ID of newID
Thanks!
Solution: Replace this.objects.newId = res[0] with this.$set(this.objects, 'newId', res[0]) and it should work.
Explanation: this.$set is just an alias to Vue.set, available within any Vue instance. Bear in mind that vm (stands for view model) in the example is the same as this and equals to Vue component instance. Also due to ES5 JS restrictions, you have to set objects' properties explicitly via Vue.set/this.$set to manually trigger re-render. This problem will be resolved when Vue 3.0 is released.
Hope this helps, if you need any clarifications - feel free to ask.
Try that:
downloadSomething(newId).then(res => {
this.$set(this.objects, 'newId', res[0])
})
you need Vue.set() when you want to define a new property to an existing object (not directly to the data), and this function will assign the new property to the built-in watcher, and it will become reactive as well.
its all being explained in the docs: https://v2.vuejs.org/v2/guide/reactivity.html
in your case, it seems to be all you need to make it work. in my example:https://jsfiddle.net/efrat19/eywraw8t/484131/ an async function fetches the data and define a new property to contain te response.
new Vue({
el: "#app",
data: {
objects:{
property1:12
}
},
methods: {
fetchDataAndAddProperty(response){
fetch('https://free.currencyconverterapi.com/api/v6/countries').then(res =>
res.json()).then(data => Vue.set(this.objects,'property2',data))
}
}
})
and the template:
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<div lg="12" v-for="(val,key,index) in objects" :key="index">
{{key}}:{{val}}
</div>
<button #click="fetchDataAndAddProperty()">fetch</button>
</div>
as you can see in the fiddle, the new property becomes reactive and being displayed as well.

Vue - instance data is not defined

I have a menu of topics (e.g. "About us", "Support") and I want to be able to tell what topic is active right now. When a topic is clicked it will become the active one. Think of it as a substitute for a router that will set an active route. This is not possible in my case since I'm working in SharePoint 2010... in a content editor. So I made the decision to have an activeTopic variable in my Vue instance.
The Vue instance
new Vue({
el: '#app',
data: {
activeTopic: '',
}
});
This is because the activeTopic has to update another component and show some data based on the active topic. Like a router showing a child route.
The topic component
Vue.component('topic', {
props: ["title"],
methods: {
setActiveTopic: function (title) {
activeTopic = title;
},
isActiveTopic: function (title) {
return activeTopic == title;
}
},
template: `
<div v-bind:class="{active: isActiveTopic(title)}" v-on:click="setActiveTopic(title)">
<p v-text="title"></p>
</div>
`
});
The setActiveTopic function works as it should when I click it; it updates the activeTopic. But the isActiveTopic function gives me this error:
Error in render: "ReferenceError: activeTopic is not defined"
What I don't understand is that I can edit the activeTopic variable but I can't make a comparison? I've tried setting a default value but it still says it is undefined.
activeTopic should be assigned to the Vue component by setting and reading this.activeTopic. As is, you have two independent activeTopic variables scoped within each of your component methods.
You'll also want to include activeTopic in the component's data block, rather than on the Vue object itself, since it's specific to the component.
(If there's a reason to put that variable on the Vue object you would either want to pass it down to the component as a prop, or else access it directly as Vue.activeTopic instead of this.activeTopic. I'm honestly not certain whether Vue would treat that last structure as a reactive value within components -- I've never had a reason to try that, and can't think of a situation where that'd be a reasonable architecture.)
Vue.component('topic', {
props: ["title"],
data() { return {
activeTopic: ''
}},
methods: {
setActiveTopic(title) {
this.activeTopic = title;
},
isActiveTopic(title) {
return this.activeTopic == title;
}
},
template: `
<div v-bind:class="{active: isActiveTopic(title)}" v-on:click="setActiveTopic(title)">
<p v-text="title"></p>
</div>
`
});
I am new to Vue, and have seen data being defined as an object in several examples.
But apparently, as per the documentation, you have to use data as a function that returns an object.
This is to ensure that all instances will have it's own version of the data object, and is not shared with all instances.
Instead of
data: {
count: 0
}
use
data: function () {
return {
count: 0
}
}
However I still don't know why in one of my component, data as an object worked, and in a child component with it's own data as an object, I got the error count is undefined;
I am assuming, the root element, (defined by new Vue({}) has to be a singleton, and is not intended to have several instances.
And since components, can have instances, the data in that needs to be defined as a function.

What is the main difference between computed and mounted in vue.js 2?

when i add computed() instead mounted() it throws an error
export default {
components: {
MainLayout
},
mounted(){
var x = document.getElementById('homeTabPanes');
// x.style.background = "blue";
console.log("check the value of x", x);
}
}
computed is an object containing methods that returns data, mounted is a life hook executed after the instance gets mounted, check out the links to the docs it have really good explanation
From the docs
..computed properties are cached based on their dependencies. A computed property will only re-evaluate when some of its dependencies have changed.
If you want data to be cached use Computed properties on the other hand mounted is a lifecycle hook, a method which is called as soon as the Vue instance is mounted on the DOM.
In-template expressions are very convenient, but they are meant for simple operations. Putting too much logic in your templates can make them bloated and hard to maintain.
That’s why for any complex logic, you should use a computed property.
Basic Example
<div id="reverseMessageContainer">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
look at the js below:
var vm = new Vue({
el: '#reverseMessageContainer',
data: {
message: 'Hello'
},
computed: {
// a computed getter
reversedMessage: function () {
// `this` points to the vm instance
return this.message.split('').reverse().join('')
}
}
})
Here we have declared a computed property reversedMessage. The function we provided will be used as the getter function for the property vm.reversedMessage:
You can open the console and play with the example vm yourself. The value of vm.reversedMessage is always dependent on the value of vm.message.
console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'
For more better understanding you can visit
https://v2.vuejs.org/v2/guide/computed.html

How to Initialize Data Properties with Prop Values

Still a little bit young in VueJS but I'm loving every bit of it. But now, fixated somewhere.
I want to initialize some values in data() using values passed via props. This is so that I can be able to mutate them later on, since it is not recommended to mutate props inside a component. In fact the official docs recommend this property initialization using prop values as shown below:
{
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
I have something like the one below:
<template>
<div class="well">
<!-- Use Prop value directly on the template: works (but of no help in initializing data) -->
Department: {{department.name}}
<!-- Use prop value but gotten via computed property: Works inside the template but not in the initialization -->
Department: {{fetchDepartment.name}}
<!-- Use the array I initialized with the prop value: Does not work -->
Department: {{this_department.name}}
</div>
</template>
<script>
export default {
name: 'test',
props: ['department'],
data() {
return {
this_department: this.department
// below does not work either
//this_department: this.fetchDepartment
}
},
created() {
// shows empty array
console.log(this.department)
},
mounted() {
// shows empty array
console.log(this.department)
},
computed: {
fetchDepartment() {
return this.department
}
}
}
</script>
As seen in the commented sections above, the initialization is not successful. Neither does the value of this.department appear either from the created() or the mounted() hooks. And note, I can see it is defined using the Chrome Vue Devtools. So my question is, how exactly should I initialize data() attributes using props values, or which is the best way of going around this issue?
I know my answer comes in late but it helps me and hopefully someone else coming here. When props' data are async:
// in the parent component
<template>
<child :foo="bar" v-if="bar" />
</template>
That way, you render the component when props are already available making it safer to follow the guide's recommended ways to initialize data value with props as so:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
Happy coding!
You CAN modify a prop. Use the '.sync' modifier. I use it frequently because it is convenient and intuitive. This requires emitting an event to update the value on the parent. I am not really sure the warning of how it results in maintenance issues.
Another method I use if I want to modify a value and not update the parent is using Lodash clone. For example (assuming its available on mounted)
mounted(){
this_department = _.clone(this.department)
}
If you consistently want to mutate the prop and have it change with the parent, then use a computed property. However, in most cases you will want to depend on the state of that data within the component and change it using other functions and thus a computed property will not be what you need.
A computed property is the simplest way to provide a mutable version of a prop, but you might not want to lose data when the prop is updated. You could use an explicit watch.
Watchers
While computed properties are more appropriate in most cases, there
are times when a custom watcher is necessary. That’s why Vue provides
a more generic way to react to data changes through the watch option.
This is most useful when you want to perform asynchronous or expensive
operations in response to changing data.
This is most useful when you want to perform asynchronous or expensive
operations in response to changing data.