Access current component data from within slot template - vue.js

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.

Related

How to send data to parent component using v-slot in vue

I am trying to use slot-scopes in my component but I am not really successful on these. So in general what I am trying to do is I have a parent component and as a slot I am sending another component (child-component). In child component there is a button which changes the boolean.
So child component is:
<button #click="changeEditMode">Change edit mode</c-button>
methods: {
changeEditMode() {
this.$emit('edit-mode', true);
},
}
Parent component:
<div>
<slot :edit-mode="editMode"></slot>
</div>
props: {
editMode: {
type: Boolean,
required: true,
},
},
And here where both of them exist:
<parent-component>
<template v-slot="scope">
{{ scope.editMode }}
<child-component
#edit-mode="scope.editMode"
/>
</template>
</parent-component>
data() {
return {
editMode: false,
};
},
So I am expecting the value of {{ scope.editMode }} will change when I click the button but nothing changes. So where I am doing wrong?
If you simply want to update the editMode variable in your third component then you don't need to use scoped slot. Simply use named slots and you can update the data like this-
Parent component-
Give your slot a name "scope" where you are planning to inject the data.
<div>
<slot name="scope"></slot>
</div>
Another component-
Put your child component inside the named slot of the parent and simply listen to the emitted event from the child and update your editMode variable.
<parent-component>
<template #scope>
{{ editMode }}
<child-component
#edit-mode="editMode = $event"
/>
</template>
</parent-component>
data() {
return {
editMode: false,
};
},
Now, as in the question you are passing data to the slots (which I feels less required according to your use case), you can do it like this-
Parent component-
<div>
<slot :editMode="editMode"></slot>
</div>
data() {
return {
editMode: false,
}
}
Another component-
<parent-component>
<template #scope="scopeProps">
{{ scopeProps }}
</template>
</parent-component>
To know more about the named slots and pass data to named slots, read here- https://vuejs.org/guide/components/slots.html#named-slots

Vue 2.x reactivity broken when using nested named slots

I ran into the following issue while working on a Vue 2.6.x project, and I just can't figure out what's going wrong :/
Hopefully, my example below explains my problem clearly.
Context:
I have a Parent component, which has a default slot, and I'd like to display some stuff in the default slot of that component.
The Parent component contains two child components both of them have a named slot called "content". These child components are nested in the Parent, the first renders the second in its #content slot, and the second renders the Parent's #default slot in its #content slot.
Observation:
The reactivity inside the Parent's default slot is broken as long as the two nested child components' slot names match. (If we reused one of the WithContentSlot components twice, it would give the same result).
Expected result:
the counter in the #default slot of the Parent should increment as well, when pressing the button
Please tell me know if you have any idea what's going wrong, or if you know a workaround. In my real project I'm working with a bunch of renderless components which expose some of their logic via slotProps on their #default slot in order to be neat & reusable, but this bug is driving me crazy, since if I nest any two of them, then I run into this issue.
const WithContentSlotOne = Vue.extend({
template: `<div><slot name="content"/></div>`,
});
const WithContentSlotTwo = Vue.extend({
template: `<div><slot name="content"/></div>`,
});
const Parent = Vue.extend({
components: {
WithContentSlotOne,
WithContentSlotTwo
},
template: `
<with-content-slot-one>
<template #content>
<with-content-slot-two>
<template #content>
<slot />
</template>
</with-content-slot-two>
</template>
</with-content-slot-one>
`,
});
const app = new Vue({
components: {
Parent
},
el: "#app",
template: `
<div>
<div>count: {{ counter }}</div>
<button #click="counter++">
increment
</button>
<Parent>
count: {{ counter }}
</Parent>
</div>`,
data() {
return {
counter: 0
};
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
<div id="app" />

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

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>

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 Multiselect does not update {{ value }} via v-model

I am using this example for Vue Multiselect "^2.0.0-beta.14" in Laravel 5.3. https://github.com/monterail/vue-multiselect/tree/2.0#install--basic-usage
The plugin renders correctly but I cannot get the selection via v-model. I am expecting #{{ selected }} to update with the current selection.
app.js
Vue.component('dropdown', require('./components/Multiselect.vue'));
VUE JS
<template>
<div>
<multiselect
v-model="value"
:options="options">
</multiselect>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
export default {
components: { Multiselect },
data () {
return {
value: null,
options: ['list', 'of', 'options']
}
}
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
HTML
<div id="app">
<h3>Dropdown</h3>
<div>
<label class="typo__label">Single select</label>
<dropdown></dropdown>
<pre class="language-json"><code>#{{ value }}</code></pre>
</div>
</div>
NB
The official example uses selected instead of value but this does not work either. According to the docs selection is replaced by value as of V2.
If you are using TypeScript Interfaces with Vue.js 2.0, avoid using a optional properties to store the value from child components. i.e. if your property is
value:? IMyCustomInterface instead use value: MyCustomObject|null and set the object to null in the constructor.
If the property is optional, it will compile fine, but child components won't update it properly.
The reason value is not showing up in root is because the data is isolated to the dropdown component. To get your data from a component to show up in the Root you need to use props.
See this question for a detailed explanation
How to get data from a component in VueJS