When one of the data attributes of Vue will change, Vue will re-execute all instruction operations - vue.js

I just want to change the value of msg,but all the instruction(v-for,v-text,v-bind) of the Vue will re-execute . How to solve this problem?
<div class="container">
<div id="app">
<div v-for="item in list">
<div> {{item.name}} </div>
<div>{{Math.random()}}</div>
</div>
<div>{{Math.random()}}</div>
<div>{{msg}}</div>
<input type="text" v-model='msg'>
</div>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
list: [{ name: "I am Tom" }, { name: "I am Mary" }],
msg: "hello"
}
})
</script>

Because you embedded Math.random() in the template for the VUe, every time the DOM for the Vue needs to be updated (as in when you change msg), a new value will be calculated. One way you might solve this is to initialize the random value when the Vue is created.
var app = new Vue({
el: "#app",
data: {
list: [{ name: "I am Tom" }, { name: "I am Mary" }],
msg: "hello",
random: Math.random()
}
})
And then use the initialized value in your template.
<div id="app">
<div v-for="item in list">
<div> {{item.name}} </div>
<div>{{random}}</div>
</div>
<div>{{random}}</div>
<div>{{msg}}</div>
<input type="text" v-model='msg'>
</div>
Another way you might solve the issue is to isolate the changes to msg in their own scope (a component).
Vue.component("message", {
props:["msg"],
template:`
<div>
<div>{{internalMessage}}</div>
<input type="text" v-model='internalMessage'>
</div>
`,
data(){
return {
internalMessage: this.msg
}
}
})
var app2 = new Vue({
el: "#app2",
data: {
list: [{ name: "I am Tom" }, { name: "I am Mary" }],
msg: "hello",
}
})
And the template:
<div id="app2">
<div v-for="item in list">
<div> {{item.name}} </div>
<div>{{Math.random()}}</div>
</div>
<div>{{Math.random()}}</div>
<message :msg="msg"></message>
</div>
Here is an example of both approaches.

Related

Conditional a href in Vuejs

I am rendering a list of store titles in VueJS, some of them have a url property, some of them don't. If the title has a url, I want to add a a href property:
<div v-for="(store, index) in stores">
<span v-if="store.link"><a :href="store.link" target="_blank">{{ store.title }}</a></span>
<span v-else="store.link">{{ store.title }}</span>
</div>
This works, but the code looks duplicated. Is there anyway to simplify the code further?
you can use component tag:
var app = new Vue({
el: '#app',
data () {
return {
stores: [
{title:'product1',link:'/products/222'},
{title:'product2'},
{title:'product3',link:'/products/333'},
{title:'product4'}
]
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<div v-for="(store, index) in stores">
<component :is="store.link?'a':'span'" :href="store.link || ''" target="_blank">{{store.title}}
</component>
</div>
</div>
I'd remove the first span element, as it's not necessary. Also, the v-else does not need the conditional statement (it's not v-else-if):
new Vue({
el: '#app',
data: {
stores: [
{ link: 'foo', title: 'foo-text' },
{ title: 'bar-text' }
]
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div v-for="(store, index) in stores" :key="index">
<a v-if="store.link" :href="store.link" target="_blank">{{ store.title }}</a>
<span v-else>{{ store.title }}</span>
</div>
</div>
You can use dynamic arguments in vue3
https://v3.vuejs.org/guide/template-syntax.html#dynamic-arguments
<a v-bind:[attributeName]="url"> ... </a>
or binding an object of attributes
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>

Vue.js Router change url but not view

I see this bug in console:
[Vue warn]: Property or method "product" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
My template id="productDetail" not receive the property "product" of the template id="product" I don't know how I can push this, please see my cod.
HTML LIST
That's ok when I click the router-link the url change to:
/product/iphone-x-64-gb for example.
<template id="product" functional>
<div class="wrapper">
<ul class="row">
<li v-for="(product, index) in products" class="col l4 m6 s12">
<div class="card-box">
<div class="card-image">
<img :src="product.images" :alt="product.images" class="responsive-img"/>
</div>
<div class="card-content">
<h3>{{ product.brand }}</h3>
<span class="price-used"><i class="used">{{ index }} gebrauchte Roomba 651</i></span>
</div>
<div class="card-action row">
<span class="col s6 price"><span>{{ product.price }}</span>
</div>
<div>
<router-link class="btn btn-default light-green darken-3" :to="{name: 'product', params: {product_id: product.id}}">meer detail</router-link>
</div>
</div>
</li>
</ul>
</div>
HTML PRODUCT DETAIL (THAT NO RECEIVE THE "product")
<template id="productDetail" functional>
<div class="row">
<div class="col s12 m6">
<img src="images/iphone-8-64-gb.jpg" alt="product.images" class="responsive-img"/>
</div>
<div class="col s12 m6">
<h3>{{ product.title }}</h3>
<h5>{{ product.price }}<h5>
<div class="col s12 m6">
<a class="waves-effect waves-light btn light-green darken-3"><i class="material-icons left">add_shopping_cart</i>kopen</a>
</div>
<div class="col s12 m6">
<router-link class="btn btn-default light-green darken-3" :to="{path: '/'}">
<span class="glyphicon glyphicon-plus"></span><i class="material-icons left">arrow_back</i>terug
</router-link>
</div>
</div>
</div>
THE .JS
var List = Vue.extend(
{
template: '#product',
data: function ()
{
return {products: [],};
},
created: function()
{
this.$http.get('https://api.myjson.com/bins/17528x').then(function(response) {
this.products = response.body.products;
}.bind(this));
},
});
const Product =
{
props: ['product_id'],
template: '#productDetail'
}
var router = new VueRouter(
{
routes: [
{path: '/', component: List},
{path: '/product/:product_id', component: Product, name: 'product'},
]
});
var app = new Vue(
{
el: '#app',
router: router,
template: '<router-view></router-view>'
});
Thank for your help.
Your props should be props: ['product'] instead of props: ['product_id']
<parent-component :product="product"></parent-component>
ChildComponent.vue
export default {
name: 'child-component',
props: ['product']
}
First activate props on the route:
var router = new VueRouter({
...
path: '/product/:product_id',
component: Product,
name: 'product',
props: true // <======= add this line
},
...
Now the product_id will be set on the Product component.
So, you want to display the whole product information, but at this moment you only have the product_id. The solution is to fetch the product:
const Product = {
props: ['product_id'],
template: '#productDetail',
data: function() { // <============ Added from this line...
return {
product: {} // add {} so no error is thrown while the product is being fetched
};
},
created: function() {
var productId = this.product_id;
// if there is a URL that fetches a given product by ID, it would be better than this
this.$http.get('https://api.myjson.com/bins/17528x').then(function(response) {
this.product = response.body.products.find(function (product) { return product.id == productId });
}.bind(this));
} // <============ ...to this line.
}
Check JSFiddle demo here of the solution above.
Alternative solution: passing the whole product as prop
Pass the product in the params: (along with product_id):
<router-link class="btn btn-default light-green darken-3" :to="{name: 'product',
params: {product_id: product.id, product: product}}">meer detail</router-link>
^^^^^^^^^^^^^^^^^^
Activate props on the route:
var router = new VueRouter({
...
path: '/product/:product_id',
component: Product,
name: 'product',
props: true // <======= add this line
},
...
Finally, add product so you can use it:
const Product = {
props: ['product_id', 'product'], // <======= added 'product' here
template: '#productDetail'
}
Demo JSFiddle for this solution here.

Any easier way to access nested object properties in Vue template?

It's convenient to group data into nested object properties. By doing this, we don't have to collect properties from the data field into an entity for later use. As in the following example,
var demo = new Vue({
el: '#demo',
data: {
level1: {
level2: {
level3_1: 'Hello',
level3_2: 'world'
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="demo">
<div class="person">
<h3>{{ level1.level2.level3_1 }}</h3>
<p>{{ level1.level2.level3_2 }}</p>
</div>
</div>
However, it's really overkill having to type the "level1.level2" prefix in order to get to the level3_x field. It'll be very cumbersome if there're loads of level3 fields.
I wonder if there is any way that I can save the work for typing level1.level2 over and over again. Does the template have any syntax so that some section is under the scope of "level1.level2"? Does Vue provide any support so that in this case the prefix "level1.level2" is assumed?
There are a couple of options.
1. Use v-for
Everything inside the v-for block is scoped to the level that you're iterating over. Do it like this:
var demo = new Vue({
el: '#demo',
data: {
level1: {
level2: {
level3_1: 'Hello',
level3_2: 'world'
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="demo">
<div class="person">
<template v-for="(l2prop, l2propName) in level1">
<h3>{{ l2prop.level3_1 }}</h3>
<p>{{ l2prop.level3_2 }}</p>
</template>
</div>
</div>
2. Use a component
Components get a subset of their parent's data, so they're automatically scoped. Do it like this:
Vue.component( "person", {
props: ['data'],
template: '<div class="person"><h3>{{ data.level3_1 }}</h3><p>{{ data.level3_2 }}</p></div>'
});
var demo = new Vue({
el: '#demo',
data: {
level1: {
level2: {
level3_1: 'Hello',
level3_2: 'world'
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="demo">
<person v-bind:data="level1.level2"></person>
</div>
The example of #jason-smith is almost right. v-for is used for arrays or lists. To make it work is necessary to put your object in list.
Following his example the better approach would be
var demo = new Vue({
el: '#demo',
data: {
level1: {
level2: {
level3_1: 'Level 3_1',
level3_2: 'Level 3_2'
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div class="person">
<template v-for="level2Obj in [level1.level2]">
<h3>{{ level2Obj.level3_1 }}</h3>
<p>{{ level2Obj.level3_2 }}</p>
</template>
</div>
</div>
There are serval ways:
Use a method that gets the same level
methods:{
getLvl3: function(nr){
return this["level"+nr]["level"+nr]["level3_"+nr];
}
{{ getLvl3(1) }}
Iterate over with v-for v-for docu
example:
<div id="demo">
<div class="person">
<template v-for="(lvl2, key) in level1">
<template v-for="(lvl3, key) in lvl2">
<h3 v-if="key === 'level3_1'>{{ lvl3 }}</h3>
<p v-if="key === 'level3_2'">{{ lvl3 }}</p>
</template>
</template>
</div>
</div>
bind to variable that is defined outside of vue:
var nested = { level1: { level2: { level3_1: 'Hello', level3_2: 'world' }}
and inside of vue component or instance:
data:{
level2: nested.level1.level2,
}
<div id="demo">
<div class="person">
<h3>{{ level2.level3_1 }}</h3>
<p>{{ level2.level3_2 }}</p>
</div>
</div>

Why can't this component access its props data?

When the following component was inline, it was able to access rowData but as a referenced template it can't:
HTML:
<div id="app">
<div>
<row-component v-for="(row, index) in rows" :row-data="row" v-on:delete-row="deleteThisRow(index)"></row-component>
</div>
</div>
<script id="theRow" type="x-template">
row component: {{rowData}}
<button #click="$emit('delete-row')">Delete3</button>
</script>
JavaScript:
Vue.component('row-component', {
props: ["rowData"],
template: '#theRow'
})
new Vue({
el: '#app',
data: {
rows: ["line1", "line2", "line3", "line4", "line5"]
},
methods: {
deleteThisRow: function(index) {
this.rows.splice(index, 1);
}
}
})
How can I get the component to recognize rowData?
https://jsfiddle.net/edwardtanguay/k1tx3z1n/
Components should have only one root element
<div id="app">
<div>
<row-component v-for="(row, index) in rows" :row-data="row" v-on:delete-row="deleteThisRow(index)"></row-component>
</div>
</div>
<script id="theRow" type="x-template">
<div>
row component: {{rowData}}
<button #click="$emit('delete-row')">Delete3</button>
</div>
</script>
See the working fiddle

Vue.js: "attach" content to two different classes?

So, I have data in my vue.js file that I want to "attach" to two different classes without having to create another Vue.js component or repeating the content again in another component. For example:
var fullViewContent = new Vue({
el: ".class-one",
data: {
name: 'Vue.js'
},
data: {
items: [{
content: "repeat this string in various places",
},
]
}
First HTML Block
<div class="class-one">
<template v-for="(item, index) in items">
<div class="container">
<h2>{{ item.content }}</h2>
</div>
</template>
</div><!-- end list view -->
Second HTML Block
<div class="different-html-block class-one">
<template v-for="(item, index) in items">
<div class="container">
<h2>{{ item.content }}</h2>
</div>
</template>
</div><!-- end list view -->
So I want the same content from my component in each of these different html blocks. Is it enough to just attach the same class to it? Something else?
You cannot apply a Vue to more than one element.
Instead, move the shared data into an object accessible to both Vues.
const shared = {
items: [
{
content: "repeat this string in various places",
},
{
content: "more data",
},
]
}
var app1 = new Vue({
el: "#app1",
data: {
name: 'Vue.js',
items: shared.items
},
})
var app2 = new Vue({
el: "#app2",
data: {
name: 'Vue.js',
items: shared.items
}
})
And the template
<div id="app1">
<div class="class-one">
<template v-for="(item, index) in items">
<div class="container">
<h2>{{ item.content }}</h2>
</div>
</template>
</div><!-- end list view -->
</div>
<div id="app2">
<div class="different-html-block class-one">
<template v-for="(item, index) in items">
<div class="container">
<h2>{{ item.content }}</h2>
</div>
</template>
</div><!-- end list view -->
</div>
Example.
If you want to avoid repeating code you can programmatically create your Vues.
each = Array.prototype.forEach;
const data = {
name: "Vue.js",
items: [
{
content: "repeat this string in various places",
},
{
content: "more data",
},
]
}
each.call(document.querySelectorAll(".class-one"), el => new Vue({el, data}))
Example.