I'm building an app with vue and vue-router. In some routes, I need to check some conditions first, if those conditions are not satisfied, then redirect to another component, so I used the activate hook on the router option of my component, and it works fine. Also, inside that same component, I have the vue created hook to load some data, the thing is that if those conditions that I mentioned before are not met, then I can't load the data in the created hook. What I would expect is that if that condition is not met, and the redirect hook was called, then the created hook wont get triggered, but what is actually happening is that whene that condition is false, then the redirect of the activate hook get calledn and also the created hook from Vue. So, more than a solution for my particular use case, I would like to know the order of execution of the hooks when using vue and vue router together.
For Vue 2.0:
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeDestroy
destroyed
Now when using vue-router 2.0, the data fetching can be done at two places, as per their doc:
Fetching After Navigation: perform the navigation first, and fetch
data in the incoming component's lifecycle hook. Display a loading
state while data is being fetched.
Fetching Before Navigation: Fetch data before navigation in the route
enter guard, and perform the navigation after data has been fetched.
For your case, you can write you data fetching logic in a function and call it inside the "created" hook of the component lifecylce. If at all the data changes with the route, then write a watcher on the $route object, which will trigger your data fetching function.
As the data hook of vue-router 0.7 is deprecated and instead the $route object has been made reactive. Read more here.
Maybe you are interested in In-Component Guards (additional hooks available in components loaded using Vue Router)
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// called before the route that renders this component is confirmed.
// does NOT have access to `this` component instance,
// because it has not been created yet when this guard is called!
},
beforeRouteUpdate (to, from, next) {
// called when the route that renders this component has changed,
// but this component is reused in the new route.
// For example, for a route with dynamic params `/foo/:id`, when we
// navigate between `/foo/1` and `/foo/2`, the same `Foo` component instance
// will be reused, and this hook will be called when that happens.
// has access to `this` component instance.
},
beforeRouteLeave (to, from, next) {
// called when the route that renders this component is about to
// be navigated away from.
// has access to `this` component instance.
}
}
https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards
If you use Vue + Vue Router and you are at RouteA and navigates from it to RouteB and each route/component register for something (e.g. receiving data supported by root) on "created" and unregister on "beforeDestroy" but when you leave RouteA its "beforeDestroy" is called after RouteB "created" so you have nothing registered! I have tested it and VUE 1 had correct order. It must be changed somewhere in VUE 2 + VUE Router 2.
Correct/expected hooks order in VUE 1 when going from RouteA to RouteB:
RouteA beforeDestroy
RouteB created
Incorrect/unexpected hooks order in VUE 2 when going from RouteA to RouteB:
RouteB created
RouteA beforeDestroy
Solution is to use "created" + "beforeRouteLeave" for VUE 2 + VUE Router 2.
Why not add console logs to each and see for yourself?
As far as I can tell without testing:
canReuse
canActivate
-- now the component instance is being created:
created
beforeCompile
compiled
-- (not sure wheither ready or activate comes first, but i guess ready)
ready
activate
data
For your particular case, the data fetching should happen in the data hook - that's what it's for, after all.
Related
I have a button that triggers a route change within my Vue application. I want the button element to be accessible in the component's $route object as the "event target" of the route change (similar to how there's an event.target in a click event).
Is that possible?
If not, what would be the best way to obtain the cause of the route change?
You are looking for Navigation Guards. There are
Global navigation guards that are called on each route
beforeEnter/resolve/leave/after depending on the type of guard you
used.
Per-route navigation guards that are used in your
router file, inside each route's declaration.
In-Component
Guards that are used inside components.
You can get the specific button that caused the route change in your component inside the In-Component Guard beforeRouteLeave(to, from). This specific guard has access to this of the component which has all the props and reactive state (including the button reference).
According to the docs, this is how each guard falls in the flow of a navigation action:
Navigation triggered.
Call beforeRouteLeave guards in deactivated components.
Call global beforeEach guards.
Call beforeRouteUpdate guards in reused components.
Call beforeEnter in route configs.
Resolve async route components.
Call beforeRouteEnter in activated components.
Call global beforeResolve guards.
Navigation is confirmed.
Call global afterEach hooks.
DOM updates triggered.
Call callbacks passed to next in beforeRouteEnter guards with instantiated instances.
As we know, to react to params changes in the same component we use beforeRouteUpdate hook or watching $route.
watching $route:
const User = {
template: '...',
watch: {
'$route' (to, from) {
// react to route changes...
}
}
}
beforeRouteUpdate Method:
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
next()
}
}
What is the difference between these two? if both are same then why vue router introduced beforeRouteUpdate?
From the documentation on beforeRouteUpdate:
called when the route that renders this component has changed, but this component is reused in the new route. For example, given a route with params /users/:id, when we navigate between /users/1 and /users/2, the same UserDetails component instance will be reused, and this hook will be called when that happens. Because the component is mounted while this happens, the navigation guard has access to this component instance.
The documentation is admittedly unclear that the hook gets called before the value of the $route object actually changes. That's the difference between this navigation hook and setting a watcher on $route, which will get called after the value of $route has changed.
Using the beforeRouteUpdate navigation guard, you can determine whether or not you want to prevent the route from changing (by not calling next()) or go to a different route entirely (by passing a different route value like next('/foo'), next({ name: 'foo' }), etc.).
Here's an example fiddle that shows when these functions get called.
As #thanksd said $route is like guard. With watching you can't prevent the route from taking action, but with beforeRouteUpdate you can do it with next function. for example you can wait untill your data is fetched then proceed to the component.
You can find more information in vue-router documentation Data Fetching.
Consider about routes below:
/form/1
/form/2
/form/3
When the address is / and I click on /form/1 VueRouter loads my component inside router-view but when I'm in /form/1 by clicking on /form/2 nothing happens!
You need to watch for param changes in the $route and perform the correct action (e.g. making an ajax call to retrieve the information for the given id), so:
watch: {
'$route' (to, from) {
// React to route change
}
}
You may also use the beforeRouteUpdate guard (see: https://router.vuejs.org/en/essentials/dynamic-matching.html)
You can also add a unique key attribute to the router-view so that vue forcefully replaces the component instead of reusing it
<router-view :key="$route.path"></router-view>
Vue.js has mounted, created, updated etc. as states I can execute functions on the first load of the page - however how I execute for example a console.log('hi'); whenever the user navigates to a particular page/route without reloading the whole app?
Vue router exposes lifecycle hooks where you can execute you code: docs
beforeEach is the way to go if you need to execute a code upon every route navigation.
Alternatively, you can watch a current route inside your main (wrapper) component and execute code inside a watcher: docs, section Reacting to Params Changes
watch: {
'$route' (to, from) {
// react to route changes...
}
}
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
}
...