Detect exiting route changes in Vue - vue.js

I'm working on a component which enables the user to undo the deletion of an item. However, the item should be deleted when the user navigates to another route. To achieve this I'm watching the route like so:
`watch: {
$route(to, from) {
if (this.showUndo === true) {
console.log('item will be deleted');
this.confirmDelete();
}
},
},
`
Unfortunately, this gets only triggered when I enter this specific route and not on exiting it. An explanation why that is or an alternative to my watch: - method would be much appreciated!
Basically I'm looking for an alternative to beforeRouteLeave since this is a sub-component and therefore I can't use Navigation Guards. Thanks!

Vue lifecycle hook- BeforeDestroy is fired right before teardown. Your component will still be fully present and functional. If you need to cleanup events or reactive subscriptions,
beforeDestroy would probably be the time to do it.
<script>
export default {
beforeDestroy() {
//Try like this
this.confirmDelete();
console.log('item will be deleted');
}
}
</script>
Ref - https://v2.vuejs.org/v2/api/#beforeDestroy

Related

beforeDestroy hook for nuxt page component

I need to emit some events before leaving a particular page. So I'm thinking of using the beforeDestroy hook to do this. But it seems not triggering the method.
// pages/view.vue
beforeDestroy() {
this.$alertEvent('finished')
}
I'm also using the keep-alive directive on the <nuxt>
How can I trigger this method effectively?
for Nuxt 3 you can use onBeforeUnmount
onBeforeUnmount(() => {
alert('the component is destroyed')
})
https://vuejs.org/api/composition-api-lifecycle.html#onbeforeunmount
i think this should work .
maybe you have problem with this.$alertEvent('finished') so you cannot see the result
beforeDestroy() {
alert('the component is destroy')
},
If your component wrapped inside - this is what Nuxt keep-alive directive does, then your component will not be destroyed.
You should use deactivated hook.
https://v2.vuejs.org/v2/api/#deactivated

How to write a single piece of code running both on route A (mounted hook) and also when arriving at route A?

Currently in order for me to do a thing X either when loading route A or arriving to route A, I need to write X twice:
watch: {
$route (to, from){
if (to.name === 'simulation-step-3-sequence') {
EventBus.$emit('actionIsSortable');
}
}
},
async mounted () {
if (this.$route.name === 'simulation-step-3-sequence') {
EventBus.$emit('actionIsSortable');
}
}
Is there a way to simplify this so I write X (the emit line) only once?
This technique is really needed only in two cases - multiple routes are using same component or dynamic routes (with parameters) ...see the docs
In this cases when the new route is using same component as the old route, Vue Router will just reuse existing component instance.
You can place a key on router-view and disable this behavior. Your site will be less effective but you can get rid of $route watcher
<router-view :key="$route.fullPath" />
Other option is to change watcher definition like this:
watch: {
$route: {
immediate: true,
handler: function(to, from) {
console.log(`Route changing from '${from}' to '${to}'`);
}
}
}
Vue will call watcher handler on route changes but also when the component is created (so you can remove the code in lifecycle hook)

Is it possible for Nuxt middleware to wait for asyncData requests to resolve?

I have a some middleware in my Nuxt app that closes a fullscreen mobile menu when a new route is clicked. What I am experiencing is the following:
user clicks nuxt-link
menu closes
asyncData delay (still shows current page)
new page is loaded upon resolved asyncData
What I would like is the following:
user clicks nuxt-link
asyncData delay (if exists)
menu closes upon new page load
Is it possible to have an asyncData watcher in Nuxt middleware?
I know I can hack this by creating a store value that tracks asyncData load, but I'd rather not have to wire up such a messy solution to each and every page that uses asyncData.
Note - not every page uses asyncData.
I think that the best option would be to leave the menu open, and then when the new page finishes loading (probably on the mounted hook) send an event or action to close the menu.
I figured this out in a more elegant solution than having to implement a store.dispatch function on each individual asyncData function.
So what I did was use a custom loading component: https://nuxtjs.org/api/configuration-loading/#using-a-custom-loading-component
However, instead of showing a progress bar or any sort of loading animation, I just used this component to set a loading state in my vuex store. Note - it forces you to have a template, but I just permanently disabled it.
<template>
<div v-if="false"></div>
</template>
<script>
export default {
methods: {
start() {
this.$store.dispatch('updateLoadingStatus', true);
},
finish() {
this.$store.dispatch('updateLoadingStatus', false);
}
}
};
</script>
Then in my middleware, I set up an interval watcher to check when loading has stopped. When stopped, I stop the interval and close the mobile menu.
export default function({ store }) {
if (store.state.page.mobileNav) {
if (store.state.page.loading) {
var watcher = setInterval(() => {
if (!store.state.page.loading) {
store.dispatch('updateMobileNav', false);
clearInterval(watcher);
}
}, 100);
} else {
store.dispatch('updateMobileNav', false);
}
}
}
This doesn't specifically have to be for a mobile menu open/close. It can be used for anything in middleware that needs to wait for asyncData to resolve. Hope this can help anyone in the future!

Vue router reloading the current route

Without reloading the whole page I need to reload the current route again (Only a component reload) in a vue app.
I am having a path in vue router like below,
{
path: "/dashboard",
name: "dashboard",
component: loadView("Dashboard"),
},
When user clicks on the Dashboard navigation item user will be redirected to the Dashboard page with vue router programmatic navigation
this.$router.push({ name: "dashboard" });
But when user already in the dashboard route and user clicks the Dashboard nav item again nothing happens. I think this is vue router's default behaviour. But I need to force reload the Dashboard component (Not to refresh the whole page).
I can't use beforeRouteUpdate since the router is not updated. Also I have tried the global before guards like beforeEach. But it is also not working.
How can I force reload the dashboard component without reloading the whole page?
It can be done in two ways.
1) Try doing vm.$forceUpdate(); as suggested here.
2) You can take the strategy of assigning keys to children, but whenever you want to re-render a component, you just update the key.
<template>
<component-to-re-render :key="componentKey" />
</template>
<script>
export default {
data() {
return {
componentKey: 0,
};
},
methods: {
forceRerender() {
this.componentKey += 1;
}
}
}
</script>
Every time that forceRerender is called, the prop componentKey will change. When this happens, Vue will know that it has to destroy the component and create a new one.
What you get is a child component that will re-initialize itself and “reset” its state.
Not mentioned here, but as the offered solutions require a lot of additional work just to get the app to render correctly, which imo is a brittle solution.. we have just implemented another solution which works quite well..
Although it is a total hack.
if (this.$route.name === redirect.name) {
// this is a filthy hack - the vue router will not reload the current page and then have vue update the view.
// This hack routes to a generic page, then after this has happened the real redirect can happen
// It happens on most devices too fast to be noticed by the human eye, and in addition does not do a window
// redirect which breaks the mobile apps.
await this.$router.push({
name: RouteNames.ROUTE_REDIRECT_PLACEHOLDER
});
}
... now continue to do your normal redirect.
Essentially, redirect to a placeholder, await the response but then immediately continue to another page you actually wanted to move toward

vue 2 lifecycle - how to stop beforeDestroy?

Can I add something to beforeDestroy to prevent destroying the component? ?
Or is there any way to prevent destroying the component ?
my case is that when I change spa page by vue-route, I use watch route first, but I found that doesn't trigger because the component just destroy..
As belmin bedak commented you can use keep-alive
when you use keep-alive two more lifecycle hooks come into action, they are activated and deactivated hooks instead of destroyed
The purpose of keep-alive is to cache and to not destroy the component
you can use include and exclude atteibutes of the keep-alive element and mention the names of the components that shoulb be included to be cached and be excluded from caching. Here is documentation
in case you want to forecefully destroy the component even if its cached you can use vm.$destroy() here
Further you can console.log in all the lifecycle hooks and check which lifecycle hook is being called
You can use vue-route navigation-guards, so if you call next(false) inside the hook, navigation will be aborted.
router.afterEach((to, from) => {
if(your condition){
next(false) //this will abort route navigation
}
})
According to this source: https://router.vuejs.org/guide/advanced/navigation-guards.html
I suggest you to do something like this with your Vue router:
const router = new VueRouter({ }); // declare your router with params
router.beforeEach((to, from, next) => {
if(yourCondition){
next(false); // prevent user from navigating somewhere
} else {
return next(); // navigate to next "page" as usual
}
});
This will prevent destroying your Vue instance on your declared condition, and it will also prevent user from navigating to another page.
Although I would consider #Vamsi Krishna "keep-alive" answer to be the proper "VueJS way" to solve this issue, I was not willing to refactor part of my code for it.
I also couldn't use the Vue router navigation guard "as-is" because in the case of beforeRouterLeave, even though using next(false) prevented the route from continuing, the component in Vue was ALREADY destroyed. Any state I had that wasn't saved would be lost, which defeats the purpose of cancelling the route change.
This wasn't what I wanted, as I needed the state of the form/settings in the component to remain (the component reloaded itself and kept the same route).
So I came up with a strategy that still used a navigation guard, but also cached any form changes/settings I had in the component in-memory, eg. I add a beforeRouteLeave hook in the component:
beforeRouteLeave (to, from, next) {
if (!this.isFormDirty() || confirm('Discard changes made?')) {
_cachedComponentData = null // delete the cached data
next()
} else {
_cachedComponentData = this.componentData // store the cached data based on component data you are setting during use of the component
next(false)
}
}
Outside the Vue component, I initialize _cachedComponentData
<script>
let _cachedComponentData = null
module.exports = {
...component code here
}
<script>
Then in the created or mounted life cycle hooks, I can set the _cachedComponentData to "continue where the user left off" in the component:
...
if (_cachedComponentData) {
this.componentData = _cachedComponentData
}
...