I have a vue.js app where I use the componenttag with :is="currentView" approach for changing the active view. I have a "state machine" that keeps track of valid transitions from a component to another component depending on some business logic. Sometimes this will tell the vue instance that the new value of currentViewis the same as the old one. If this happens, the component will not be reloaded. Is there any way I can force the component to reload even if the view is the same? That is I want the data to be reloaded, and the lifecycle hooks being executed.
You can add a unique key attribute to your component definition so that it correctly triggers all the component lifecycle methods.
Your component definition should look something like this:
<component :is="view" :key="unique_key"></component>
Helpful links:
Reloading a dynamic component
Vue 'key' api reference
This seems to do the trick, hower it causes the view to flash, is there a way I can avoid this?
const oldView = this.currentView
this.currentView = engine.advance()
if(this.currentView == oldView) {
//force reload!
this.currentView = ''
this.$nextTick(function () {
this.currentView = oldView
})
}
Related
If I’m on a page with the URL 'http://localhost:8080/item' and I’m clicking on the same link on this page, then the page does not reload.
I need to make that if I click on the same link, the page will reload.
My link:
<nuxt-link :to="/item">
Any insight will be welcome. Thanks!
Use key, something like:
<router-view :key="$route.params.yourCustomParam"/>
Also you can use something like:
<router-link :to="{ params: { yourCustomParam: Data.now } }" replace>link</router-link>
Remember to is passed router.push() and it accept an object also. Doing that, it is more declarative and controllable. I'm using this to decide if the page of component should be rerendered since they will based on id params obtained from URL entry, and my child component can still using nesting .
I recently tried to solve a similar issue and to overcome this I used Vuex with :key (ref).
Firstly, in your store you need a state property such as:
export const state = () => ({
componentUpdates: {
item: 0,
//can add more as needed
}
})
In general, you could use only one property across the app if you prefer it that way. Just remember that later on, the key value needs to be unique - that is in the case if you used this property for two or more components within one page, for example. In this case, you could do something like this :key="$store.getters.getComponentUpdates.item+'uniqueString'"
then a getter:
export const getters = {
getComponentUpdates(state) {
return state.updateComponent;
}
}
finally a mutatation:
export const mutations = {
updateComponent(state, payload) {
return state.componentUpdates[payload.update]++
}
}
Now we can utilise the reactive :key wherever needed.
But first in your nuxt-link lets add an event to trigger the mutation, note the usage of #click.native to trigger the click event:
<nuxt-link #click.native="$store.commit('updateComponent', { update: 'item'})" :to="/item">
Now in the item page, for example. Let's imagine there is a component that needs to be updated. In this case we would add :key to it:
<my-item :key="$store.getters.getComponentUpdates.item" />
That is it. As you can see this solution utilises the benefits of nuxt-link but also allows us to selectively update only parts of our page that need updates (we could update the entire page this way as well if needed).
In case if you needed to trigger the logic from mounted or initial load in general, then you could use computed property and :key to your div container, right inside the <template> of your page.
Add :key to the div:
<template>
<div :key="$store.getters.getComponentUpdates.item"></div>
</template>
Create computed property:
computed: {
updateItemPage() {
//run your initial instructions here as if you were doing it in mounted then return the getter
this.initialLoadMethod()
return this.$store.getters.getComponentUpdates.item
}
}
The final touch, which is not crucial but can be implemented in order to reset the state property:
export const mutations = {
updateComponent(state, payload) {
return state.componentUpdates[payload.update] >= 10
? state.componentUpdates[payload.update] = 0
: state.componentUpdates[payload.update]++
}
}
I have a call in my created method which has an await.
I want to know that the results of that call are loaded so that i can conditionally show/hide things in the DOM.
Right now it looks like the DOM is being rendered before that method has completed. But I though that methods in created were called before the DOM rendered?
You're correct in assuming that the created hook runs before the component mounts. However, the lifecycle hooks are not waiting for async calls to complete. If you want to wait for that call to be completed and data to load, you can do so by using a Boolean that you set to true when your data has loaded.
Your template:
<div v-if='dataLoaded'>Now you can see me.</div>
in your vue instace
export default {
data () {
return {
dataLoaded: false
}
},
created () {
loadMyData().then(data => {
// do awesome things with data
this.dataLoaded = true
})
}
}
This way you can keep your content hidden until that call has resolved. Take care with the context when you handle the ajax response. You will want to keep this as a reference to the original vue instance, so that you can set your data correctly. Arrow functions work well for that.
I have a custom map component, which wraps a Openlayers 4 instance. This component which I am forced to use, is used multiple places across my SPA. The initialization process is quite long, so I would like to keep one instance of the map available, and move it between views when I need to. Problem is that the state doesen´t update within the component when it has moved.
I´ve boiled the problem down to this fiddle: https://jsfiddle.net/j16d4yto/
When moved on the same router-view the state updates fine (click the ‘Change text’ button). But when the router-view changes, and the component is moved with appendChild to the new div, the state freezes, and you can´t update the text variable anymore.
This is how I move the component from one element to another:
this.$root.$on('showMoveableComponent', function(element) {
element.appendChild(thisElement);
this.text = 'Changed text2';
});
I bet I am doing something wrong here, and probably also approaching this problem in the wrong way?
Thanks!
It's not working because of when router-view changed your MoveableComponent has been destroyed only its DOM element still referenced by you. You can test by print something in destroyed lifecycle callback function.
So this mean you can solve this by using built-in keep-alive component:
<keep-alive>
<router-view></router-view>
</keep-alive>
Example
The keep-alive component will cache everything which may not good in some other cases.
In my opinion the better way to solve this is create another Vue instance and move it.
const MoveableComponent = new Vue({
el: '#some-id',
template: `...`,
data: { ... },
methods: {
changeText() {
...
},
moveTo(element) {
element.appendChild(this.$el)
}
}
})
Example
I have a custom directive for checking user roles for some component will be shown or not.
Vue.directive("permission", {
bind(el, binding) {
Vue.nextTick(() => {
el.vFillMarkerNode = document.createComment('');
el.parentNode.insertBefore(el.vFillMarkerNode, el.nextSibling);
if (Auth.canSee(binding.value)) {
el.textContent = binding.value;
} else {
el.parentNode.removeChild(el);
}
});
},
This will work but in the component which is not shown, It will get the data from api (on component created hook) even user does not have permission.
<mycomp v-permission="['Admin']"></mycomp>
Is there any way to tell the vue, if my directive condition does not fit, do not fire related component 'created' hook, so do not get data from api?
I don't think this is the right way to do what you are trying to do. Since you are depending on the root-element on the component in order to do your checks. You are doing your checks after the component has already mounted. hence the Vue.nexTick().
There are a two ways you could solve this issue.
Run your logic in the Vuex Store and use mapGetters in the parent component to get the permissions in all of your components.
Bind your permissions to the root element and do v-if="$root.permissions.includes('admin')" on your component
I have two <router-view/>s: main and sidebar. Each of them is supplied with a component (EditorMain.vue and EditorSidebar.vue).
EditorMain has a method exportData(). I want to call this method from EditorSidebar on button click.
What is a good way of tackling it?
I do use vuex, but i don't wanna keep this data reactive since the method requires too much computational power.
I could use global events bus, but it doesn't feel right to use it together with vuex (right?)
I could handle it in root of my app by adding event listener to router-view <router-view #exportClick="handleExportData"> and then target editor component, but it does not feel right as well as later i could need 100 listeners.
Is there any good practice for this? Or did i make some mistakes with the way app is set up? Did is overlooked something in documentation?
After two more years of my adventure with Vue I feel confident enough to answer my own question. It boils down to communication between router views. I've presented two possible solutions, I'll address them separately:
Events bus
Use global events bus (but it doesn't feel right to use it together with vuex)
Well, it may not feel right and it is surely not a first thing you have to think about, but it is perfectly fine use-case for event-bus. The advantage of this solution would be that the components are coupled only by the event name.
Router-view event listeners
I could handle it in root of my app by adding event listener to router-view <router-view #exportClick="handleExportData"> and then target editor component, but it does not feel right as well as later i could need 100 listeners.
This way of solving this problem is also fine, buy it couples components together. Coupling happens in the component containing <router-view/> where all the listeners are set.
Big number of listeners could be addressed by passing an object with event: handler mapping pairs to v-on directive; like so:
<router-view v-on="listeners"/>
...
data () {
return {
listeners: {
'event-one': () => console.log('Event one fired!'),
'event-two': () => console.log('The second event works as well!')
}
}
You could create a plugin for handling exports:
import Vue from 'vue'
ExportPlugin.install = function (Vue, options) {
const _data = new Map()
Object.defineProperty(Vue.prototype, '$exporter', {
value: {
setData: (svg) => {
_data.set('svg', svg)
},
exportData: () => {
const svg = _data.get('svg')
// do data export...
}
}
})
}
Vue.use(ExportPlugin)
Using like:
// EditorMain component
methods: {
setData (data) {
this.$exporter.setData(data)
}
}
// EditorSidebar
<button #click="$exporter.exportData">Export</button>