Vuejs prevent data to be null - vue.js

In my component I've data that is succesptible to be invalid
<template>
<p> field </p>
<p> {{ maybeInvalid }} </p>
<p> other field </p>
<template>
var component = {
data: function () {
return {
maybeInvalid: xxx
}
}
I've 3 ideas to fix that :
1 - Use v-if v-else in template
<template>
<p> field </p>
<p v-if="isValid(maybeInvalid)"> {{ maybeInvalid }} </p>
<p v-else> invalid </p>
<p> other field </p>
<template>
...
methods: {
isValid: function (data) {
return data != null;
}
}
2 - Make a computed value
<template>
<p> field </p>
<p> {{ computedIsValid }} </p>
<p> other field </p>
<template>
computed: {
computedIsValid: function() {
return isValid(maybeInvalid) ? maybeInvalid : "invalid";
}
}
3 - build a component
var isValidCompoenent = {
props: ['maybeInvalid'],
template: `
<p v-if="isValid(maybeInvalid)"> {{ maybeInvalid }} </p>
<p v-else> invalid </p>`
methods: {
isValid: function (data) {
return data != null;
}
}
}
and use this component
<template>
<p> field </p>
<is-valid-component :maybeInvalid="maybeInvalid" />
<p> other field </p>
<template>
...
components: {
isValidComponent: isValidComponent
}
I would like to know what's the best (more idiomatic) solution to solve that pattern
Thanks

It's hard to choose which would be more idiomatic; these are all idiomatic solutions to your issue using Vue. In other words, I would not be unhappy to see any of them in a Vue application.
That said, I would steer away from the component solution in this case unless you are likely to use the is-valid component in many places, not just the one component you are talking about here. Additionally, Joe Developer reading your code is not going to know right off the bat exactly what that does; they'll have to go read the code for is-valid.
Using a computed or a v-if are pretty much equivalent in my eyes in this case with one exception. Using the same argument above, Joe Developer reading your template is going to immediately know what happens with v-if but will have to look at the computed to fully understand it. This, however is a very minor thing.

Related

Vue: calling method on v-if

I'm working on a Vue project started by someone else and don't have a lot of experience with Vue myself.
With this code:
<div :class="{ 'results': isResults }">
<ais-stats v-slot="{ nbHits }">
<ais-hits
v-if="nbHits"
v-slot="{ items }"
// resultsExist – how is this called?
>
<Product v-for="item in items" :key="item.objectID" :product="item" />
</ais-hits>
<p v-else class="text-center">
No results.
</p>
</ais-stats>
</div>
data() {
return {
isResults: false
};
},
methods: {
resultsExist() {
isResults: true;
}
}
I want to set a class on the parent div when nbHits returns results (from Vue instant-search).
The code is working fine in terms of displaying results or the no results message. But I don't know what the syntax is for calling the resultsExist method.
Assuming that nbHits is an object
You can use dynamic class binding
<div :class="{'className': nbHits }"></div>

vuejs render part of template inside different elements without repeating

I am new to Vuejs. This is what I need to do.
<div v-for="r in records">
<div v-if="r.something">
<div id="x">
{{ r. something}}
more of r here.
</div>
</div>
<div v-else id="x">
same div as in the block above.
</div>
</div>
What I want do is not define div with id x two times as it is huge.
Make your 'div' a component and refer to it in both places.
There are many ways to define your component. This is example shows just one. If you are using WebPack, use a single file component. You can then have your script, html, and css all in one file that gets precompiled. That's the best way to manage your 'huge' div. Then you can continue to refactor and break it up into more components.
const myComponent = {
template: "<div :id='id'>HELLO, my id is {{id}}. r.foo is {{r.foo}} </div>",
props: {
id: String
},
data() {
return {
r: {
foo: 'bar'
}
}
}
}
<div v-for="r in records">
<div v-if="r.something">
<my-component id='x' />
</div>
<div v-else id="x">
<my-component id='x' />
</div>
</div>

x-template has trouble displaying value on the v-for

I had this issue while trying to render html into a vue component.
I am trying to insert component html through x-template. The issue is when I was trying to display the value {{i.value}} like this it was throwing error on console.
<script type="text/x-template" id="first-template">
<div>
<ul>
<li v-for="i in dataCollection">{{ i.id }}</li>
</ul>
</div>
</script>
Vue.component('menu', {
template: '#first-template',
data() {
return {
dataCollection: [{"id":"01"}, {"id":"02"}, {"id":"03"}],
}
}
});
The error on console was:
But when I was giving value as attribute like:
<script type="text/x-template" id="first-template">
<div>
<ul>
<li v-for="i in dataCollection" :id="i.id"></li>
</ul>
</div>
</script>
it works perfect.
Anyone know any fix ?
You should not put script/x-template tages inside of the element that you mount to the main instance to. Vue 2.0 will read all of its content and try to use it as a template for the main instance, and Vue's virtualDOM treats script/x-template's like normal DOM, which screws everthing up,
Simply moving the template out of the main element solved the problem.
Source
This is a suggestion, not a answer.
As #DmitriyPanov mentioned, you'd better bind unique key when using v-for.
Another issue is you'd better to use non built-in/resevered html elements.
so change component id from menu to v-menu or else you like.
Then simulate similar codes below which are working fine.
I doubt the error is caused by some elements of dataCollection doesn't have key=id (probably you didn't post out all elements). You can try {{ 'id' in i ? i.id : 'None' }}.
Vue.component('v-menu', { //
template: '#first-template',
data() {
return {
newDataCollection: [{"id":"01"}, {"id":"02"}, {"id":"03"}, {'xx':0}],
dataCollection: [{"id":"01"}, {"id":"02"}, {"id":"03"}]
}
}
});
new Vue({
el: '#app',
data() {
return {testProperty: {
'test': '1'
}}
},
methods:{
test: function() {
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<v-menu></v-menu>
</div>
<script type="text/x-template" id="first-template">
<div>
<div style="float:left;margin-right:100px;">
<p>Old:</p>
<ul>
<li v-for="(i, index) in dataCollection" :key="index">{{ i.id }}</li>
</ul>
</div>
<div>
<p>Adjusted:</p>
<ul>
<li v-for="(i, index) in newDataCollection" :key="index">{{ 'id' in i ? i.id : 'None' }}</li>
</ul>
</div>
</div>
</script>
I think the problem here lies in the placement of the X-Template code (I had the same issue). According to the documentation:
Your x-template needs to be defined outside the DOM element to which Vue is attached.
If you are using some kind of CMS, you might end up doing just that.
What helped me in that case was (based on your example):
Placing the X-template script outside the #app
passing the collection as a prop to the v-menu component:
<v-menu v-bind:data-collection="dataCollection"></v-menu>
list dataCollection as a prop inside the v-menu component:
Vue.component('v-menu', { //
template: '#first-template',
props: [ "dataCollection" ],
...
});
I hope that helps anyone.
In 2.2.0+, when using v-for with a component, a key is now required.
You can read about it here https://v2.vuejs.org/v2/guide/list.html#v-for-with-a-Component

Display value emitted from Vue component

I created two separated Vue components and I able to emit a message thru a bus.
How can I render/display the message in the component that receives the message.
Example of the Vue component that receives the message:
<template>
<div v-model="cars">
Car model: {{ model }}
<input type="button" #click="display" value="example" />
</div>
</template>
<script>
export default {
data() {
return {
cars: null
}
},
mounted() {
bus.$on('CARS_LOADED', (cars) => {
this.cars = cars;
});
},
methods: {
display()
{
console.log(this.cars);
}
}
}
</script>
I can successfully emit and received the message, however the car model is not updated. I checked the message received and it contains the "model" key with a right value.
I cannot see any error in the Vue console and however if I replace "{{ model }}" by "{{ cars }}" I can see the full message object updated.
I am using Vue 2.x.
Update:
I enclose an example:
https://jsfiddle.net/kvzvxk4f/1/
As you can see in the example I cannot render an specific field from the object, however I can render the object as string.
I think that you are misunderstanding some parts of the vue syntax.
How to access properties of an object:
You just need to write {{ car.model }} to access a property of an object.
How to iterate through an array in a template:
If you want to display all the cars in your template, you should write:
<div v-for="car in cars">
{{ car }}
</div>
As you see, the v-for directive allows you to iterate through an array.
What is v-model?
v-model is used to bind a variable to an input or a component.
<template>
<div>
<input type="text" v-model="foo" />
</div>
</template>
<script>
export default {
data () {
return {
foo: 'bar'
}
}
}
</script>
In that case, the foo property will be bound to the input text.
Last point:
In your case, to make it work, you also need to create a root element for your template, because a template can't have multiple root elements:
<template>
<div>
<div v-for="car in cars">
{{ car }}
</div>
</div>
</div>
I found the answer.
I just have to type property separated by ".". Like for example {{cars.model}}.
<template id="compo2">
<div>
<div>
{{ field.name }}
</div>
<div>
Received: {{ field }}
</div>
</div>
</template>
Example:
https://jsfiddle.net/zuhb7s8q/3/

Vue JS - Access root computed property inside a component

I'm attempting to access a computed property from the root Vue instance and access it inside a component. The <p class="currency"> element which is output outside of the component template outputs {{ currency }} correctly, but when trying to access {{ currency }} inside of the component nothing is output. I have tried setting currency as a prop but this doesn't appear to make any difference. I'm sure there must be a way to access the root Vue instance from within the component, something like {{ vm.currency }} but again I have tried this to no avail.
Here is the HTML.
<div id="app">
<ul class="plans">
<plan-component : name="Basic" ></plan-component>
<plan-component : name="Rec" ></plan-component>
<plan-component : name="Team" ></plan-component>
<plan-component : name="Club" ></plan-component>
</ul>
<template id="plan-component">
<li>
<h2 class="plan-name">{{ name }}</h2>
<h3 class="plan-cost">{{ currency }}</h3>
</li>
</template>
<p class="currency">{{ currency }}</p>
</div><!-- end #app -->
Here is the JS. The variable countryCode is defined elsewhere in my app, but like I said {{ currency }} is working outside of the component so this isn't an issue.
Vue.component('plan-component', {
template: '#plan-component',
props: {
name: String,
}
});
var vm = new Vue({
el: '#app',
computed: {
currency: function() {
if(countryCode === 'GB') {
return "£";
} else {
return "$";
}
}
}
});
For anyone with the same issue, you simply need to define $root before the property. So in my example instead of this...
<h3 class="plan-cost">{{ currency }}</h3>
...it needs to be this...
<h3 class="plan-cost">{{ $root.currency }}</h3>
The VueJS docs do talk about this under the Parent Chain section of Components.