What is the Vue equivalent to Angular's "::" one-time binding? - vuejs2

I saw that mixins are a possibility but not only is it overly verbose and clunky to achieve the desired functionality, it'd be super convenient to have a "::" equivalent that is written in the view/template code.
<template>
<div>
<div>I am a dynamic/observed binding: {{ integerCounter }}</div>
<div>I am a one-time binding: {{ ::integerCounter }}</div>
<button #click="integerCounter += 1">Increment</button>
</div>
</template>
In the above snippet, assuming integerCounter is instantiated at 0, the one-time binding will display "0" even if the button is clicked. The dynamic one will update on render.
Does such a thing exist?

Excellent example illustrating what you're after but I'm afraid Vue doesn't have anything like this that I'm aware of.
The general advice would be to use two explicit data properties. One could even be a prop which initialises the local copy
<template>
<div>I am a dynamic/observed binding: {{ counter }}</div>
<div>I am a one-time binding: {{ initialCounter }}</div>
<button #click="counter++">Increment</button>
</template>
<script>
export default {
props: {
initialCounter: {
type: Number,
default: 0
}
},
data: ({ initialCounter }) => ({
counter: initialCounter
})
}
</script>

Related

Access current component data from within slot template

I have the following vue component
<template>
<CardGroup>
<template #headerRight>
<div>Total items: {{ this.total }}</div>
</template>
</CardGroup>
</template>
export default {
data() {
return {
total: 0
};
}
}
I don't understand the scoping problem. The this in the slot template is null and I cannot access the this.total data property. I can use that property outside the slot template though.
Why this is null inside the slot template?
Vue binds properties automatically, please go through it.
data binding
<div>Total items: {{ total }}</div>
Well, the solution was somewhat simple. I just had to omit this
<div>Total items: {{ total }}</div>
It turns out vue binds properties automatically to the _vm.

Vue/Nuxt Component updates and rerenders without any data changing

I have two components, Carousel.vue and Showcase.vue. I'm testing them both in a page like this:
<template>
<main>
<app-showcase
before-focus-class="app-showcase__element--before-focus"
after-focus-class="app-showcase__element--after-focus"
>
<div class="test-showcase" v-for="n in 10" :key="n">
<img
class="u-image-cover-center"
:src="`https://picsum.photos/1000/1000?random=${n}`"
alt=""
/>
<div>Showcase numero {{ n }}</div>
</div>
</app-showcase>
<div class="u-layout--main u-margin-vertical--4">
<div>
<app-button #click="changeRequestTest(4)">Test Request 4</app-button>
<app-button #click="changeRequestTest(10)">Test Request 10</app-button>
<app-carousel
content-class="u-spread-horizontal--main"
center
:request-element="requestTest"
scroll-by="index"
#index-change="onIndexChange"
>
<template #header>
<h4>Placeholder images</h4>
<div>
Carousel heading h4, showing item number {{ index + 1 }}.
</div>
</template>
<template #default>
<img
:src="`https://picsum.photos/100/80?random=${n}`"
:data-carousel-item-name="n === 10 ? 'giovanni-rana' : ''"
alt=""
v-for="n in 20"
:key="n"
/>
</template>
</app-carousel>
</div>
</div>
</main>
</template>
<script>
export default {
data() {
return {
n1: 20,
n2: 20,
isAnimationOver: false,
index: 0,
requestTest: null,
};
},
methods: {
changeRequestTest(n) {
this.requestTest = n;
},
onIndexChange(e) {
this.requestTest = e;
},
logTest(msg = "hello bro") {
console.log(msg);
},
logElement(e) {
console.log(e);
},
},
created() {
this.requestTest = this.$route.query.hero;
},
};
</script>
Both components use a parameter called index, which basically registers the position (in the children array) of the element that is being focused/shown/highlighted.
...
data() {
return {
index: 0,
showBackButton: true,
showForwardButton: true,
lockScroll: false,
};
},
...
Carousel actually has a prop, requestElement, that allows the carousel to be scrolled to a specific element (via number or element name), and is used in combination with the event "index-change" to update the value in the parent component.
Now that's the problem: every time requestElement updates (there is a watcher in Carousel.vue to follow that), the Showcase component rerenders.
That's a huge problem because the Showcase component applies specific classes on the mounted hook, and on rerender, all that classes are lost. I solved the problem by calling my class-applying-methods in the update hook, but I don't particularly like this performance-wise.
Basically, if I remove any reference to requestElement, everything works as intended, but as soon as that value changes, the showcase rerenders completely. I tried to change props/params names, to use dedicated functions instead of inline scripts, nothing works. No common value is shared, neither via props or vuex, so this makes it even weirder.
Any idea why this happens?
EDIT: I tried the solution from this question, as expected no change was detected in Showcase component, but it still gets updated when requestElement is changed in Carousel.

VueJS: v-model on <span> element ? How to handle that?

I have this simple component displaying user info:
<div class="m-card-user__details">
<span class="m-card-user__name m--font-weight-500">
{{ firstname }} {{ lastname }}
</span>
<a class="m-card-user__email m--font-weight-300 m-link">
{{ loginEmail }}
</a>
</div>
Script
<script>
export default {
data () {
return {
loginEmail : this.$store.getters.user.loginEmail,
firstname: this.$store.getters.user.firstName,
lastname: this.$store.getters.user.lastName,
}
}
};
</script>
Problem is that if another component change the value of the firstname property in the VueX store, I see that the value is well updated on the store but not on my component here..
How can i set the 2 ways bindings on a element ?
Attaching a store variable directly to data() will break the 2-way-binding.
Use a computed property for that, like:
computed: {
loginEmail() {
return this.$store.getters.user.loginEmail;
}
}
and then use the computed on the span, like {{ loginEmail }}, as you would normally.
Improvement: If you want, you can return the entire ...getters.user (as a object) with
computed, like:
computed: {
user() {
return this.$store.getters.user;
}
}
and then use it on your span like {{ user.loginEmail }} and so on.
This will save you some lines, increase readability and possibly a tiny bit of performance.
You can also directly use $store in your template.
<div class="m-card-user__details">
<span class="m-card-user__name m--font-weight-500">
{{ $store.getters.user.firstName }} {{ $store.getters.user.lastName }}
</span>
<a class="m-card-user__email m--font-weight-300 m-link">
{{ $store.getters.user.loginEmail }}
</a>
</div>
I haven't tried if this works with getters, but I don't see a reason why it wouldn't. Now you could maybe argue it's an anti-pattern, but I'd prefer it over having a computed property solely for this purpose.
See also https://github.com/vuejs/vuex/issues/306.
Edit: Because you mention 2-way binding: You shouldn't do this to update the $store, use actions and mutations instead. In this case, it's fine as it's essentially a 1-way binding where the state flows to the innerHTML of your <span>s.

Vue JS loop with different components

I am using Vue JS to make a list that has one generic list item component. If there exists a none generic component that meets the correct type a custom component will be used.
<email-queue-item v-for="item in queue"
:key="item.id"
:item="item"
v-if="type == 'EmailMessage'"></email-queue-item>
<queue-item v-for="item in queue"
:key="item.id"
:item="item"
v-else></queue-item>
The code above better illustrates what I am trying to do. The problem I seem to have is due loops first creating two list and then checks the conditional. Is there a way in due to pick the right component vase on the type and then loop through the list?
The data Used to display these components is like this:
{
name: Email,
type: EmailMessage,
data:[
{...},
{...},
...
]
}
Dynamic components make this pretty easy in the template:
<component
:is="type == 'EmailMessage' ? 'email-queue-item' : 'queue-item'"
v-for="item in queue"
:key="item.id"
:item="item"
/>
If I undersstand correctly, you'd like v-for with dynamic component.
so check Vue Official Guide: dynamic component, then the demo will be like below which uses v-bind:is:
Vue.config.productionTip = false
Vue.component('email-queue-item', {
template: `<div><h3 :style="{'background-color':color}">Email: {{color}}</h3></div>`,
props: ['color']
})
Vue.component('message-queue-item', {
template: `<div><h1 :style="{'background-color':color}">Message: {{color}}</h1></div>`,
props: ['color']
})
new Vue({
el: '#app',
data() {
return {
items: [
{'component':'email-queue-item', 'color':'red'},
{'component':'message-queue-item', 'color':'blue'},
{'component':'email-queue-item', 'color':'green'}
]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div v-for="(item, index) in items" :key="index" :color="item.color" :is="item.component"></div>
</div>

Getting ref of component in async v-for

I have a list of items that don't get created until after an async call happens. I need to be able to get the getBoundingClientRect() of the first (or any) of the created items.
Take this code for instance:
<template>
<div v-if="loaded">
<div ref="myItems">
<div v-for="item in items">
<div>{{ item.name }}</div>
</div>
</div>
</div>
<div v-else>
Loading...
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
items: []
}
},
created() {
axios.get('/url/with/some/data.json').then((response) => {
this.items = response.data;
this.loaded = true;
}, (error) => {
console.log('unable to load items');
});
},
mounted() {
// $refs is empty here
console.log(this.$refs);
// this.$refs.myItems is undefined
}
};
</script>
So, I'm trying to access the myItems ref in the mounted() method, but the this.$refs is empty {} at this point. So, therefore, I tried using a component watch, and various other methods to determine when I can read the ref value, but have been unsuccessful.
Anyone able to lead me in the right direction?
As always, thanks again!!
UPDATES
Added a this.$watch in the mounted() method and the $refs still come back as {}. I then added the updated() method to the code, then was able to access $refs there and it seemed to work. But, I don't know if this is the correct solution?
How does vuejs normally handle something like dynamically moving a div to an on-screen position based on async data? This is similar to what I'm trying to do, grab an element on screen once it has been rendered first (if it even should be rendered at all based on the async data), then access it to do something with it (move it to a position)?
Instead of doing on this.$refs.myItems during mounted, you can do it after the axios promise returns the the response.
you also update items and loaded, sou if you want to use watch, you can use those
A little late, maybe it helps someone.
The problem is, you're using v-if, which means the element with ref="myItems" doesn't exist yet. In your code this only happens when Axios resolves i.e. this.loaded.
A better approach would be to use a v-show.
<template>
<div>
<div v-show="loaded">
<div ref="myItems">
<div v-if="loaded">
<div v-for="item in items">
<div>{{ item.name }}</div>
</div>
</div>
</div>
</div>
<div v-show="!loaded">
Loading...
</div>
</div>
</template>
The difference is that an element with v-show will always be rendered and remain in the DOM; v-show only toggles the display CSS property of the element.
https://v2.vuejs.org/v2/guide/conditional.html#v-show