how can i access data property in html template using loop in vue js - vue.js

i'm trying to use data property commentsToShow in my html template to limit the amount of data that displays on my webpage
this is my template
<div v-if="index < products.length" v-for="(commentIndex, index) in computedProduct">
<div class="title pt-4 pb-1">{{products[index].title}}</div>
</div>
if i add commentsToShow in my for loop i get one product but the computed products doesn't work same way the other way round
this my script tag
<script>
export default {
data() {
return {
commentsToShow: 1,
totalComments: 0,
};
},
computed: {
computedProduct() {
let tempRecipes = this.products;
if (this.filterPrice !== "true");
}
};
</script>
if i change computed property to commentsToShow this the error i get in my console
The computed property "commentsToShow" is already defined in data.
please how can i get the value of commentToShow in my template

according to vue's official docs it's not recommended to use v-for and v-if on the same element
try using v-if on a wrapper div or template element
<div v-for="(commentIndex, index) in computedProduct">
<template v-if="index < products.length">
<div class="title pt-4 pb-1">{{products[index].title}}</div>
</template>
</div>
v-if has higher priority so it's executed first and index will not be defined yet.
also you have to return something on your computed property function in order to use it

You can use the slice method on a computed property, like this:
<script>
export default {
data() {
return {
commentsToShow: 1,
allComments: []
};
},
computed: {
listComments() {
return allComments.slice(0, commentsToShow);
}
};
</script>
You can also use pages to show the comments, in this case you can return like this:
return allComments.slice((currentPage - 1) * commentToShow, commentsToShow);
The first argument of slice is the start index, the second is the number of elements to get

The computed property "commentsToShow" is already defined in data.
Equivalently how you cannot have more than one variable with the same name defined in a scope. A computed property cannot have the same name as an existing data property. Essentially, they co-exist in the same namespace, thus they have to be unique.
You have a name clash, and that is what the error is saying.

Related

How to get selected option from selected componets in Vue.js?

<div class="fligtInput flex flex-no-wrap">
<SelectFlight
v-model="flight.model1" placeholder="from"
></SelectFlight>
<SelectFlight
v-model="flight.model2" placeholder="to"
></SelectFlight>
</div>
I use (select option) SelectFlight Component and this returns a JSON object. I wanna take the selected options for both components in parent component.
I will take a stab at an example showing multiple options, knowing I don't have the full context of your specific problem, but I am sure this example will get you pointed in the right direction.
I took liberty with object names, and assumed values in JSON objects. Obviously you will ned to adjust this for your specific use case and data.
<template>
<div class="i-am-the-parent">
<!--
Display flight from name
-->
<div
v-if="flights.from"
class="display-text-from"
>
{{ flights.from.airportName }}
</div>
<SelectFlight
v-model="flights.from"
placeholder="from"
/>
<!-- Bind flight to "airportName" as a prop in parent -->
<ParentWrapperComponent
class="display-text-to"
:airport-name="flights.to ? flights.to.airportName : null"
>
<!--
Nested in ParentWrapperComponent
-->
<SelectFlight
v-model="flights.to"
placeholder="to"
/>
</ParentWrapperComponent>
<!--
Kitchen Sink
Here I am demonstrating multiple approaches:
1. Merge the data via a computed property and pass as a property named airports
2. Simply pass the flights data property as a flights property
3. Simply pass the properties "flights.from" data as property "from" and "flights.to" data as property "to"
-->
<SomeComponentToShowBoth
:airports="combineValues"
:flights="flights"
:from="flights.from"
:to="flights.to"
/>
</div>
</template>
<script>
import SelectFlight from '#/path/to/component/SelectFlight'
export default {
components: {
SelectFlight
},
data () {
return {
flights: {
from: null,
to: null
}
}
},
computed: {
combineValues () {
// this will return both json objects merged into one.
// It will update everytime this.flights.from or this.flights.to changes
return Object.assign({}, this.flights.from, this.flights.to)
},
combineFlightNames () {
// Perhaps you just want to pass the names?
return {
from: flights.from ? flights.from.airportName : null,
to: flights.to ? flights.to.airportName : null
}
}
}
}
</script>
References:
Vue Computed Properties
Vue Component Props
Vue Reactivity

VueJS: Altering prop directly vs. this.[prop] in v-if

I built a vue component that get's a number value via prop from the outside laravel blade, like this:
<my-custom-template :mynumber="{{$numbervalue}}" :list:{{$alist}}></my-custom-template>
inside the template I have a v-for list and the prop:
props:{
list:Array,
mynumber: Number,
[..]
}
and
<template>
<ul>
<li v-for="item in list">{{item}}<span v-if="item.id == mynumber">active</span></li>
</ul>
</template>
Whenever the ID of the item is the same as the value mynumber, I want the "active" tag/span to be displayed.
Now in this template I also have a method that sends an axios request and on success it alters the value of the prop "mynumber", so the list should rerender:
axios.post('/api/someurl', this.obj)
.then(res => {
this.mynumber= res.data[something]; // returns a new number from the db.
})
.catch(error => { [..]
};
Issue: If I use this.mynumber in the list's v-if condition, the "active" tag is never being shown. If I use directly == mynumber then it works, but I cannot alter it with the axios response.
How should I approach this correctly?
How can I alter the initial prop, with the new value from the axios call?
First, you shouldn't be modifying props directly, as mentioned in this prior Stack Overflow post.
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "propRoomSelected"
Second, as seen in the Vue documentation for conditionals, you do not use this within templates, the this is inferred.
Now, to get to the meat of your question, how to look at either a prop or a new value when rendering. Here's how I'd do it.
<template>
<ul>
<li v-for="item in list">{{item}}<span v-if="isCurrent(item.id)">active</span></li>
</ul>
</template>
<script>
export default {
props: ['list', 'myNumber'],
data() {
return {
myNewNumber: undefined
}
},
methods: {
isCurrent(itemId) {
return itemId == (myNewNumber || myNumber)
}
}
}
</script>
Edit:
Note that there is a difference between
return itemId == (myNewNumber || myNumber)
and
return (itemId == myNewNumber) || (itemId == myNumber)
The first one "short circuits" a comparison against myNumber once myNewNumber becomes anything "truthy". Read more here.
Don't mutate props directly. Use this.$emit (Docs: https://v2.vuejs.org/v2/guide/components-custom-events.html) instead to change the myNumber in the parent. myNumber will then automatically update in the child component.

How can I rerender my vue template after changing v-for list?

So I'm trying to change a list based on a whether the elements are considered active or not. I do this through a computed data array. Basically a Search Function. However my template does not rerender and update automatically, even though I try to force it with this.forceUpdate().
This is my v-for in template:
<ion-list>
<div v-for="project in activeProjects" :key="project">
<ion-item v-if="checkemail!=project.creator">
<ion-button #click="openProjectPage(project.id)">{{ project.name }}</ion-button>
</ion-item>
</div>
</ion-list>
This is my computed array. The Log returns the correct things.
computed: {
activeProjects: function() {
return this.myprojects.filter(function(u){
console.log(u);
return u.active
})
}
}
And this is where I update the activity. The Log also returns the correct things.
search: function(){
for(var i=0; i<this.myprojects.length; i++){
if(this.myprojects[i].name.includes(this.searchinput)){
this.myprojects[i].active=true;
console.log(this.myprojects[i])
}
}
this.$forceUpdate();
}
Grateful for any help
I understand what you're attempting with the $forceUpdate, but I'm not certain that's the intended behavior here. In particular, by directly modifying the property of an Object in an Array, I believe Vue is missing the changes completely, so it doesn't know what to forceUpdate.
(See these links to read more on when Vue does / doesn't recognize mutations to Objects and Arrays)
TBH I've never attempted to use forceUpdate in this way, but I have done some Array mutation in a spreadsheet-like scenario before and it was a pain... I would avoid it if at all possible.
Rather than modifying a property in the array, I'd compute the filter on-the-fly using a method. You should get the reactivity you want because you're calculating, not mutating, the properties of the list of projects.
<script>
export default {
props: ['myprojects'],
data() {
return {
searchinput: ''
}
},
computed: {
activeProjects() {
return this.myprojects.filter(this.isInSearch)
}
},
methods: {
isInSearch(project) {
return project.name.includes(this.searchinput)
}
}
}
</script>
Vue caches nodes based on :key value. You're passing the entire object, you should be using a unique property on your project.
Try yo use name or an unique id if you have one.
<ion-list>
<div v-for="project in activeProjects" :key="project.name">
<ion-item v-if="checkemail!=project.creator">
<ion-button #click="openProjectPage(project.id)">{{ project.name }}</ion-button>
</ion-item>
</div>
</ion-list>```

closure within v-for, attribute interpolation

I have this basic setup
<div v-for="n in 4">
<some-component #on-some-event="onSomeEvent(n)"></some-component>
</div>
the on-some-event is dispatched within some-component. but I need to know which of these components sent the message. with the setup above, only n is passed into the method. and the data that the event sends is nowhere.
I'd like to interpolate the function so that the method looks like this
onSomeEvent(n){
return (obj)=>{
console.log(`component ${n} sent ${obj}`);
};
}
but wrapping onSomeEvent with {{}} throws a warning: attribute interpolation is not allowed in Vue.js directives and special attributes.
I could just pass the n index into the component but that seems less elegant because I may not have the ability to modify some-component
I am somewhat new to Vue, so perhaps I am missing some core functionality for this type of thing?
<div v-for="n in 4">
<some-component #on-some-event="onSomeEvent | pass n"></some-component>
</div>
....
filters: {
pass(handler, n) {
return function() {
handler()(n, ...arguments)
}
}
},
methods: {
onSomeEvent() {
console.log(...arguments)
}
}
https://jsfiddle.net/2s6hqcy5/2/
You didn't miss anything, the message is correct, in Vue, you won't be able to use interpolation like that.
http://vuejs.org/guide/syntax.html#Interpolations
However, you may want to change how you manage events and pass data between components. In your example, you can just bind the data like this:
<div v-for="n in 4">
<some-component :n="n"></some-component>
</div>
In your component, declare the prop:
Vue.component('some-component', {
props: ['n'],
...
Now, inside each component, you have the n available like any other property (http://vuejs.org/guide/components.html#Props).
Then when dispatching your event, you can call it like this, with no need for a param:
onSomeEvent()
On the event itself, you can access the n:
console.log('Component' + this.n + ...)
https://jsfiddle.net/crabbly/mjnjy1jt/

Binding method result to v-model with Vue.js

How do you bind a method result to a v-model with Vue.js?
example :
<someTag v-model="method_name(data_attribute)"></someTag>
I can't make it work for some reason.
Thank you.
Years later, with more experience, I found out that is it easier to bind :value instead of using v-model. Then you can handle the update by catching #change.
Edit (per request):
<input :value="myValue" #change="updateMyValue">
...
methods: {
updateMyValue (event) {
myValue = event.target.value.trim() // Formatting example
}
}
And in a child component:
// ChildComponent.vue
<template>
<button
v-for="i in [1,2,3]">
#click="$emit('change', i) />
</template>
// ParentComponent.vue
<template>
<child-component #change="updateMyValue" />
</template>
<script>
import ChildComponent from './child-component'
export default {
components: {
ChildComponent
},
data () {
return {
myvalue: 0
}
},
methods: {
updateMyValue (newValue) {
this.myvalue = newValue
}
}
}
</script>
v-model expressions must have a get and set function. For most variables this is pretty straight forward but you can also use a computed property to define them yourself like so:
data:function(){
return { value: 5 }
},
computed: {
doubleValue: {
get(){
//this function will determine what is displayed in the input
return this.value*2;
},
set(newVal){
//this function will run whenever the input changes
this.value = newVal/2;
}
}
}
Then you can use <input v-model="doubleValue"></input>
if you just want the tag to display a method result, use <tag>{{method_name(data_attribute)}}</tag>
Agree with the :value and #change combination greenymaster.
Even when we split the computed property in get/set, which is help, it seems very complicated to make it work if you require a parameter when you call for get().
My example is a medium sized dynamic object list, that populates a complex list of inputs, so:
I can't put a watch easily on a child element, unless I watch the entire parent list with deep, but it would require more complex function to determine which of the innter props and/or lists changed and do what fromthere
I can't use directly a method with v-model, since, it works for providing a 'get(param)' method (so to speak), but it does not have a 'set()' one
And the splitting of a computed property, have the same problem but inverse, having a 'set()' but not a 'get(param)'