What is <router-view :key="$route.fullPath">? - javascript

I'm completely new to Vue.js and I think I have a bit of understanding of how a router works with things like:
<router-link to="/">
But I am not really understanding what the following line does:
<router-view :key="$route.fullPath"></router-view>
I believe router-view by itself makes sure the content is displayed but what does the key part mean?

See Special Attributes - key
It can also be used to force replacement of an element/component instead of reusing it. This can be useful when you want to:
Properly trigger lifecycle hooks of a component
Trigger transitions
$route.fullPath is defined as
The full resolved URL including query and hash.
If you bind key to $route.fullPath, it will always "force a replacement" of the <router-view> element / component every time a navigation event occurs.
As mentioned above, this is most probably done in order to trigger a transition / animation.

In Vue-Router 4.0, this is implemented using a scoped slot. According to the docs the following is necessary to force a route to reload.
<router-view v-slot="{ Component, route }">
<component :is="Component" :key="route.path" />
</router-view>
This is necessary for integrating with Vue 3 transitions.
<router-view v-slot="{ Component, route }">
<transition name="fade">
<component :is="Component" :key="route.path" />
</transition>
</router-view>
Note: Putting the :key="route.path" directly on the <router-view> can cause subtle routing issues. Such as a route with a redirect triggering twice.

Related

Vue doesnt update component on dynamic variable condition change

I am working with Vuejs. I want to render components based on value of variable val.
My component looks like this
<template v-if="this.$val===1">
<component1 />
</template>
<template v-if="this.$val===2">
<component2 />
</template>
I have defined a global variable val using Vue.prototype and I am updating it using onclick function,where I am changing value of val to 2 but after clicking it doesnt show component2 instead of component 1.
Define val globally in main.js using following line of code
Vue.prototype.$val = 1;
Can someone please help me with this. Thanks
td,dr; Vue.prototypeis not reactive.
I'm going to enumerate issues as I observe them, hoping you'll find them useful.
You're not specifying which version of Vue you're using. Since you're using Vue.prototype, I'm going to guess you're using Vue 2.
Never use this in a <template>.
Inside templates, this is implicit (sometimes formulated: "inside templates this doesn't exist"). What would be this.stuff in controller, is stuff in the template.
You can't conditionally swap the top level <template> of a Vue component. You need to take the conditional either one level up or one level down:
one level up would be: you create separate components, one for each template; declare them and have the v-if in their parent component, rendering one, or the other
one level down would be: you move the v-if inside the top level <template> tag of the component. Example:
<template><!-- top level can't have `v-if` -->
<div v-if="val === 1">
val is 1
<input v-model="val">
</div>
<div v-else>
val is not 1
<input v-model="val">
</div>
</template>
<script>
export default {
data: () => ({ val: 1 })
}
</script>
Note <template> tags don't render an actual tag. They're just virtual containers which help you logically organise/group their contents, but what gets rendered is only their contents.1 So I could have written the above as:
<template><!-- top level can't have v-if -->
<template v-if="val === 1">
<div>
val is 1
<input v-model="val">
</div>
</template>
<template v-else>
<template>
<template>
<div>
val is not 1
<input v-model="val">
</div>
</template>
</template>
</template>
</template>
And get the exact same DOM output.
For obvious reasons, <template> tags become useful when you're working with HTML structures needing to meet particular parent/child constraints (e.g: ul + li, tr + td, tbody + tr, etc...).
They're also useful when combining v-if with v-for, since you can't place both on a single element (Vue needs to know which structural directive has priority, since applying them in different order could produce different results).
Working example with what you're trying to achieve:
Vue.prototype.$state = Vue.observable({ foo: true })
Vue.component('component_1', {
template: `
<div>
This is <code>component_1</code>.
<pre v-text="$state"/>
<button #click="$state.foo = false">Switch</button>
</div>
`})
Vue.component('component_2', {
template: `
<div>
This is <code>component_2</code>.
<pre v-text="$state"/>
<button #click="$state.foo = true">Switch back</button>
</div>
`})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue#2.7.10/dist/vue.min.js"></script>
<div id="app">
<component_1 v-if="$state.foo"></component_1>
<component_2 v-else></component_2>
</div>
Notes:
<div id="app">...</div> acts as <template> for the app instance (which is, also, a Vue component)
Technically, I could have written that template as:
<div id="app">
<template v-if="$state.foo">
<component_1 />
</template>
<template v-else>
<component_2 />
</template>
</div>
, which is pretty close to what you were trying. But it would be slightly more verbose than what I used, without any benefit.
I'm using a Vue.observable()2 for $state because you can't re-assign a Vue global. I mean, you can, but the change will only affect Vue instances created after the change, not the ones already created (including current one). In other words, Vue.prototype is not reactive. This, most likely, answers your question.
To get past the problem, I placed a reactive object on Vue.prototype, which can be updated without being replaced: $state.
1 - there might be an exception to this rule: when you place text nodes inside a <template>, a <div> wrapper might be created to hold the text node(s). This behaviour might not be consistent across Vue versions.
2 - Vue.Observable() was added in 2.6.0. It's a stand-alone export of Vue's reactivity module (like a component's data(), but without the component). In v3.x Vue.Observable() was renamed Vue.reactive(), to avoid confusion/conflation with rxjs's Observable.
global variables are accessed in template without this keyword which means $val===1 will work.
Solution 1:
<template>
<component1 v-if='$val === 1' />
<component2 v-else/>
</template>
This will work.
But you could make use of dynamic components in your case.
Solution 2:
<template>
<component :is='currentComponent'/>
</template>
<script>
\\imports of your components will go here
export default {
name: 'app',
components: {
component1, component2
},
computed:{
currentComponent(){
return this.$val === 1?component1:component2;
}
}
}
</script>
Dynamic components are more performant and helps you maintain state of component.

Vue .native on events sometimes needed and sometimes not (within the same component)

I just experienced something I don't understand with a running vue app.
I have a button, which is included in muliple parent components.
I just added some eventlisteners for mouseover and mouseout.
In one case I have to call the function with .native and when the button component is used in another parent, I have to leave it away to be called.
For the sake of completeness I leave everything of my template in here:
<template>
<component
:is="computedType"
ref="button"
:class="[$options.name, { 'is-reversed': reversed, 'is-naked': naked, 'is-disabled': disabled, 'has-inverted-textcolor': invertedTextColor }]"
:style="backgroundColor ? `background-color: ${backgroundColor}` : false"
:disabled="disabled"
:aria-disabled="disabled"
:to="!external ? to : false"
:aria-controls="ariaControls"
:aria-expanded="ariaExpanded"
:target="!target && external && mailto ? '_blank' : target"
:type="computedType === 'button' ? buttonType : false"
:rel="external ? 'noopener' : false"
#click="click"
#mouseover.native="triggerHover"
#mouseout.native="releaseHover"
#mouseover="triggerHover"
#mouseout="releaseHover"
>
<slot />
</component>
</template>
You can see that I now added the #mouseover and #mouseover.native to cover both cases.
I would really be interested in what causes the difference here.
To be honest, I don't fully understand the part in the vue docs.
I also realized that I register a difference in how I can access the style properties of my DOM element.
I gave it a reference: ref="button" and I would normally access it via:
this.$refs.button.style.backgroundColor
But in the case where I need the .native to call the method, I also need $el to access the stlye propperty: this.$refs.button.$el.style.backgroundColor.
I now not only don't understand what's exactly going on but I also have a problem that my button component will fail in one of the two cases, because I can't access the style property in the same way.
The only difference in how I use the component is, that in one case the button appears in a v-for-loop. Everything else is exactly the same.
Is this causing the different behaviour?
Thank you for some background information about that.
Cheers
Edit
Here are my parent component templates:
This is where I must use #mouseout without native:
<template>
<div class="TwoColumnModule">
<div class="TwoColumnModule__text">
<BRichtext v-if="component.text" class="TwoColumnModule__richtext TwoColumnModule__BRichtext" :content="component.text" />
<BBtn
v-if="component.button_checkbox"
class="TwoColumnModule__BBtn"
:inverted-text-color="component.button_text_color"
:background-color="component.button_color"
:target="component.button_link.target"
:to="checkLinkIfInternal(component.button_link.url) ? prepareNuxtLink(component.button_link.url) : component.button_link.url"
:external="typeButton"
:href="checkLinkIfInternal(component.button_link.url) ? false : component.button_link.url"
:hover-color="component.button_color_hover"
>
{{ component.button_link.title }}
</BBtn>
</div>
<!-- eslint-disable vue/no-v-html -->
<div
v-else
class="TwoColumnModule__video"
v-html="component.vimeo_video"
/>
</div>
</template>
And this is where I need .native and must access the reference via
this.$refs.button.$el:
<template>
<div class="Storyboard">
<BHeading v-if="component.main_title" :level="3" class="Storyboard__heading Storyboard__BHeading">{{ component.main_title }}</BHeading>
<div v-for="(item, index) in component.four_column_layout" :key="index" class="Storyboard__item--four-columns">
<BRichtext v-if="item.text" class="Storyboard__richtext Storyboard__BRichtext" :content="item.text" />
<BBtn
v-if="item.button.button_checkbox"
class="TwoColumnModule__BBtn"
:inverted-text-color="item.button.button_text_color"
:background-color="item.button.button_color"
:target="item.button.button_link.target"
:to="checkLinkIfInternal(item.button.button_link.url) ? prepareNuxtLink(item.button.button_link.url) : item.button.button_link.url"
:external="checkIfTypeButton()"
:href="checkLinkIfInternal(item.button.button_link.url) ? false : item.button.button_link.url"
:hover-color="item.button.button_color_hover"
>
{{ item.button.button_link.title }}
</BBtn>
</div>
</div>
</template>

Conditional scoped slot templates in vue

Can I not do something like this?
<template v-if="options.columns">
one thing...
</template>
<template v-else slot-scope="scope">
something else. scope doesn't work here. :(
</template>
When I try to do this, scope becomes undefined. The moment I remove v-if, else and use scope it works as expected.
I was facing with the exact same problem, what's worse, mine is failing silently - I didn't see any errors.
Solution #1 move if else condition to the inner scope of <template>
As David Japan pointed out, you can check if else condition in the inner scope of slot-scope:
<template slot-scope="scope">
<span v-if="options.columns">one thing...</span>
<span v-else>something else.</span>
</template>
Solution #2 use v-slot instead of slot-scope
<template v-slot="scope" v-if="options.columns">
one thing...
</template>
<template v-slot="scope" v-else>
something else.
</template>
However I don't know why v-slot fixes it, I searched both in the official documentation and online but no clues.
The solution is a workaround:
Create 2 different SFC for each possible use case and do conditional rendering.
Vuetify example:
// App.vue
<div>
<ListMD v-if="this.$vuetify.breakpoint.name === 'md'" />
<ListXS v-if="this.$vuetify.breakpoint.name === 'xs'" />
</div>
In Vue.js component you cannot have more than one template tag.
Why not using 'is' property for dynamic component ?
<template>
<div :is='my-component'></div>
</template>
So you can load a component dynamicaly.
https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components

Bootstrap-Vue - making a subcomponent work inside a b-modal component

So for some reason when I add a subcomponent to the <b-modal> component it doesn't display the modal properly. It looks like the :is="currentModal" isn't being read properly.
<b-modal size="lg" v-model="currentModal" #ok="currentModal = null">
<component :is="currentModal" :email="email"></component>
</b-modal>
Basically currentModal is the current component to display. if I change it so that currentModal is a string, like such, it still doesn't work properly either:
<b-modal size="lg" v-model="currentModal" #ok="currentModal = null">
<Register v-if="currentModal=='register'":email="email"></Register>
<Login v-if="currentModal=='login'" :email="email"><Login>
</b-modal>
If I remove the b-modal tags altogether the Login and Register components work like they're supposed to, so the problem seems to be that the Register and Login components aren't reading currentModal properly from within b-modal, for some reason. Any help would be appreciated!
You need to use <b-container> as a direct child element of <b-modal>.
In this case, it'll be something like below:
<b-modal size="lg" v-model="currentModal" #ok="currentModal = null">
<b-container>
<component :is="currentModal" :email="email"></component>
</b-container>
</b-modal>

Force whole template re-rendering when data is changed in dom-repeat

I have this dom-repeat template in polymer
<template is="dom-if" if="true">
<typeahead items="{{item.children}}"></typeahead>
</template>
<template is="dom-repeat" id="level" items="{{item.children}}">
<li on-click="itemClickHandler" id$="[[getItemId(item)]]" class$="{{isItemSelected(item, selectedItem)}}" >
<template is="dom-if" if="[[!toShowColumnTypeahead()]]"><span>{{getItemLabel(item)}}</span></template>
<template is="dom-if" if="[[toShowColumnTypeahead()]]">
<template is="dom-if" if="[[!item.searchTerm]]" restamp="true">
<span class="f">{{getItemLabel(item)}}</span>
</template>
<template is="dom-if" if="{{item.searchTerm}}" restamp="true">
<span class="">{{item.currentSearchLabel.prefix}}<span class="bold">{{item.currentSearchLabel.highlight}}</span>{{item.currentSearchLabel.suffix}}</span>
</template>
</template>
<button class$="[[getItemOpenerClass(item)]]" on-click="openClickHandler" key$="[[getItemId(item)]]">Open</button>
<template is="dom-if" if="{{_hasChildren(item, showChevron)}}">
<span class="chevron"><i class="fa fa-angle-right"></i></span>
</template>
</li>
</template>
The data for item.children is changing from typeahead polymer component and its notifying it. however if the items.children is modified and was still part of previous result then it is not redrawn on dom, I want it to be redrawn on DOM every time there is any change in the list irrespective of whether it was part of previous change or not. Right now Polymer only redraws the changed elements on the DOM. I tried so many thing from net from this.set to manually calling the render method by query selecting the template.
I'm not entirely sure I follow you 100% but you could try using restamp on the dom-repeat, forcing it to destroy and recreate elements every time item.children is changed.
Ignore this, restamp actually only works on dom-if
Render will synchronously force rendering the changes that will usually be done asynchronously, but it only picks up changes made through the Polymer API, so you might want to make sure the changes made to your item.children are made through the Polymer API and not just native array function (i.e the thing setting it should do something along the line of this.push('theArray', newItem) instead of the usual theArray.push(newItem)).
Edit: to further clarify:
calling render "just" forces to re render the changes synchronously, but this shouldn't force rendering more stuff than the usual asynchronous rendering
your issue probably comes from how the array is updated. Use this.splice, this.push, this.pop, this.unshift or this.shift to mutate your array. Link to the doc

Categories