is special attribute vs v-if / v-show - vue.js

is special attribute is:
<!-- Component changes when currentTabComponent changes -->
<component v-bind:is="currentTabComponent"></component>
conditional rendering is:
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
I know the different between using v-if and v-show, but I don't know the difference between using a list of v-ifs for different cases vs using the is special attribute. When should I use them?
Does is work like v-if or v-show? I mean, does it render all the components anyways? Is is like a syntactic sugar for a list of subsequent v-ifs?

is would be useful if the list of v-ifs would all render a component if true.
Like so:
<template v-if="component == "firstComponent">
<first-component></first-component>
<template v-else-if="component == "secondComponent">
<second-component></second-component>
</template>
<template v-else-if="component == "thirdComponent">
<third-component></third-component>
</template>
This can then be reduced to:
<component :is="component"></component>
Concerning your second question
Wether component :is works like v-if or v-show depends on wether you wrap it in <keep-alive> or not. Read the documentation on this here. Note though, that using <component> a component only gets created until it is necessary the first time, wether you use <keep-alive> or not.
So:
v-if (re)creates the component everytime the condition is met.
<component :is="..."> creates the component everytime the condition is met (like v-if).
<keep-alive><component :is="..."></keep-alive> creates the
component at most 1 time (but possibly 0).
A component with only v-show on it is created exactly once.

Related

Vue3: Using v-for with keep-alive

My Problem
I have a variable number of Tab components; there can be 1 or 10, but only one is going to be displayed at a time. However, I want to cache them between switches. I thought I could put a v-for loop inside a <keep-alive> but according to the docs: https://v3.vuejs.org/api/built-in-components.html#keep-alive:
Note, <keep-alive> is designed for the case where it has one direct child component that is being toggled. It does not work if you have v-for inside it. When there are multiple conditional children, as above, <keep-alive> requires that only one child is rendered at a time.
Question
This seems like a pretty standard use-case, so I'm wondering if there's an easier way about this that I'm missing. Is there a best practice to rendering a variable number of components inside the <keep-alive> component?
Update: Kind of an answer
I was able to separate the Tab logic from the v-for, which means the Tabs get generated elsewhere, while the TabContent is the only thing inside <keep-alive>. Kind of like so:
<keep-alive>
<TabContent tab-name="name" />
</keep-alive>
Then internally, TabContent uses the tab-name prop to grab the data it needs.
Admittedly it feels like a good solution but I'm open to other ways to solve this.
Update 2.0: For Dynamic Components, use :key
The last thing I figured out; my Tab titles are dependent on filenames, and the TabContent is generated from the file, so it takes a filename props. My content was dynamic.
You still can't use a v-for inside the <keep-alive>, but you can use keys still:
<keep-alive>
<template>
<component
:is="'TabContent'"
:key="currentTabTitle"
v-bind="{
filename: currentTabTitle
}"
/>
</template>
</keep-alive>
Take a look at Dynamic Components with keep-alive
<div id="dynamic-component-demo" class="demo">
<button
v-for="tab in tabs"
:key="tab"
:class="['tab-button', { active: currentTab === tab }]"
#click="currentTab = tab"
>
{{ tab }}
</button>
<!-- Inactive components will be cached! -->
<keep-alive>
<component :is="currentTabComponent"> </component>
</keep-alive>
</div>

Passing slots through from Parent to Child Components

I have built a user-defined component (async-select) on top of another component (vue mutliselect) like this:
https://jsfiddle.net/2x7n4rL6/4/
Since the original vue-multiselect component offers a couple of slots, I don't want to loose the chance to use them. So my goal is to make these slots available from inside my custom component. In other words, I want to something like this:
https://jsfiddle.net/2x7n4rL6/3/
But that code oes not work.
However, if I add the slot to the child component itself, it works just fine (which you can see from the fact that options become red-colored).
https://jsfiddle.net/2x7n4rL6/1/
After surfing the web, I have come across this article, but it does not seem to work
Is there any way in VueJS to accomplish this ?
Slots can be confusing!
First, you need a template element to define the slot content:
<async-select :value="value" :options="options">
<template v-slot:option-tmpl="{ props }">
<div class="ui grid">
<div style="color: red">{{ props.option.name }}</div>
</div>
</template>
</async-select>
Then, in the parent component, you need a slot element. That slot element itself can be inside of another template element, so its contents can be put in a slot of its own parent.
<multiselect
label="name"
ref="multiselect"
v-model="localValue"
placeholder="My component"
:options="options"
:multiple="false"
:taggable="false">
<template slot="option" slot-scope="props">
<slot name="option-tmpl" :props="props"></slot>
</template>
</multiselect>
Working Fiddle: https://jsfiddle.net/thebluenile/ph0s1jda/

vuejs conditional rendering leaving extra html markup

I am new to VueJS and I have a simple HTML markup where I iterate through some objects and render them in html like so:
<div v-for="item in some_counter">
<p v-if="item.some_param1 !== 'None'">
[[ item.some_param2 ]]
</p>
</div>
However, I notice that even when the condition evaluates to false, I see an extra HTML <div></div> markup. This seems very odd to me, coming from the Django world.
How do I avoid this extra markup?
The v-if applies to the element you place it on. So if you want to conditionally include the <div> you need to put the v-if on the <div> (or a parent element). It won't remove the <div> just because it is empty.
Technically you can have both v-for and v-if on the same element but it is generally discouraged as it can be confusing trying to understand which is applied first (see https://v2.vuejs.org/v2/guide/list.html#v-for-with-v-if). Instead you should include a wrapping <template> for the v-for:
<template v-for="item in some_counter">
<div v-if="item.some_param1 !== 'None'">
<p>
[[ item.some_param2 ]]
</p>
</div>
</template>
The <template> tag is special and won't add an extra element to the finished DOM.
An alternative approach would be to filter the list of items in a computed property and then iterate over the filtered list in your template.

How can I remove Vue transition dynamically

I have a table body which is declared as animation block.
in some particular scenarios i want remove the transition to the table body.is there any way to do that.
HTML
<transition name="condition ? 'fade' : ''">
<p>Hello</p>
</transition>
Specify the condition in the name attribute, when condition is true (or equal any value) run the "name" argument, in another case leave it empty and the transition will not work
You can use template with if-else condition:
<template v-if="condition">
<transition> <!-- if condition matched, use transition -->
<<html element>>
</transition>
</template>
<template v-else>
<<html element>>
</template>

VueJS: How To Collapse Elements In A For Loop Separately?

I like to toggle the topic.text property. I want to collapse AND expand it alternately.
I have the following setup:
<template v-for="topic in $store.state.forum.topics">
<div class="topic">
<div class="date">{{ topic.user }}, {{ topic.date }}</div>
<span class="title">{{ topic.title }}</span>
<div class="text">{{ topic.text }}</div>
</div>
</template>
You could do something like:
<template v-for="topic in $store.state.forum.topics">
<div class="topic" #click="toggleCollapsation">
<div class="date">{{ topic.user }}, {{ topic.date }}</div>
<span class="title">{{ topic.title }}</span>
<div class="text" v-show="isCollapsed">{{ topic.text }}</div>
</div>
</template>
<script>
export default {
data() {
return {
isCollapsed: false
};
},
methods: {
toggleCollapsation() {
this.isCollapsed = !this.isCollapsed;
}
}
};
</script>
Instead of v-show you can also use v-if. The differences are (from the official docs):
v-if vs v-show
v-if is “real” conditional rendering because it ensures that event
listeners and child components inside the conditional block are
properly destroyed and re-created during toggles.
v-if is also lazy:
if the condition is false on initial render, it will not do anything -
the conditional block won’t be rendered until the condition becomes
true for the first time.
In comparison, v-show is much simpler - the
element is always rendered regardless of initial condition, with just
simple CSS-based toggling.
Generally speaking, v-if has higher toggle
costs while v-show has higher initial render costs. So prefer v-show
if you need to toggle something very often, and prefer v-if if the
condition is unlikely to change at runtime.
https://v2.vuejs.org/v2/guide/conditional.html#v-if-vs-v-show
I wouldn't read the topics directly from the $store global though. Assuming you're using VueX, I would use mapGetters in the parent view and feed the topic's component through props.
For added sugar you can take a look at Vue transitions: https://v2.vuejs.org/v2/guide/transitions.html