Why use beforeRouteEnter instead of mounted? - vue.js

Why does the beforeRouteEnter navigation guard exist in vue-router? Are there instances where beforeRouteEnter will be fired, but mounted will not be? If not, in what instance would you prefer using beforeRouteEnter to mounted?

The mounted is a lifecycle hook of any Vue component, it'll always be triggered. The idea of beforeRouteEnter or any other lifecycle hook added by the vue-router is to allow you to control your application.
For example, let's say that you have a route called bar which has a really specific validation logic that only allow the user to enter in it if the previous route was foo, you may insert that validation logic inside this hook instead of checking every route change in the global guard.
export default {
name: 'Bar',
beforeRouteEnter(to, from, next) {
if (from.name === 'foo') {
next(); // Calling next allow the route to proceed
} else {
next(false); // Don't allow the navigation
// or
next({
name: 'foo',
query: {
from: 'bar'
}
}); // Redirect to any desired route as navigation made in $router
}
}
}

Related

onBeforeEnter does not exist in vue-router#next

I am trying to switch over to vuejs3 and the new vue-router.
Now I see that beforeRouteEnter is not exposed:
// same as beforeRouteUpdate option with no access to `this`
onBeforeRouteUpdate((to, from, next) => {
// your logic
console.log("Hello world") //this is only triggered if the id changes
next()
})
So my question is: How can I trigger the initial axios-requests on a specific route like /dashboard as I did before?
It's not possible to execute code in setup before the route enters because at the time of setup the navigation has already been confirmed.
Another option #1
You can still use the options api, so you could still use beforeRouteEnter:
setup() {
...
},
beforeRouteEnter(to, from, next) {
console.log(to);
}
Another option #2
Use beforeEnter in the router:
{
path: '/foo',
component: Foo,
beforeEnter(to) {
console.log(to)
}
}

created hook not waiting for beforeEach action promise to resolve

I have a beforeEach route guard that calls fetchWorkspaces action. This action performs a request to Axios and its response populates the state.
However, when the state is called from the created hook in the component and I refresh the page I do not get the values in the console, but the observer instead.
created() {
console.log(this.workspace) # returns {__ob__: Observer}
}
The action is returning a promise but the created hook is not waiting for that promise to resolve.
This is the hook from the router:
router.beforeEach((to, from, next) => {
store.dispatch('fetchWorkspaces').then(() => {
next()
})
}
And this is the action and its mutation:
export default {
state: {
workspaces: []
},
mutations: {
SET_WORKSPACES(state, workspaces) {
state.workspaces = workspaces.workspaces
}
},
actions: {
fetchWorkspaces({ commit }) {
return Vue.axios.get('/workspaces').then(response => {
commit('SET_WORKSPACES', response.data)
})
}
}
The created is called after the beforeEach hook. I do not understand why this behavior is happening and how to fix it.
The reason I want to get access the newly state data from created is to call other actions that will fetch resources based on this state data.
See this and that. I think the Vue is created first before your router register beforeEach. So, you should use beforeEach before you initiate Vue instance.

VueJS Adding to lifecycle hooks on every component

So I have a loader screen in my app, and the idea is to show the loader screen on the beforeCreate hook so the user can't see the stuff being rendered, and then on the mounted hook remove the loader screen.
This is fun and nice for when you have two or three view/components, but currently my app has a lot more than that, and adding it to each component/view doesn't make much sense for me.
So I was wondering, is there any way to add something to the beforeCreate and mounted hooks on a global scope. Something like this:
main.js
Vue.beforeCreate(() => {
//Show the loader screen
});
Vue.mounted(() => {
//Hide the loader screen
});
That way it would be applied to every component and view
You can use mixins for this purposes, and import in components.
//mixins.js
export default {
beforeCreate() {},
mounted() {}
}
And in component add mixins: [importedMixins]
You will have access to 'this'.
Actualy you can use and vuex to (mapGetters, mapActions etc.)
If you don't want include mixins in every component, try to use vue plugins system (https://v2.vuejs.org/v2/guide/plugins.html):
MyPlugin.install = function (Vue, options) {
// 1. add global method or property
Vue.myGlobalMethod = function () {
// something logic ...
}
// 2. add a global asset
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// something logic ...
}
...
})
// 3. inject some component options
Vue.mixin({
created: function () {
// something logic ...
}
...
})
// 4. add an instance method
Vue.prototype.$myMethod = function (methodOptions) {
// something logic ...
}
}
And use your plugin like this Vue.use(MyPlugin, { someOption: true })
There is something very silimar to your request in vue-router. I've never used afterEach but beforeEach works perfectly.
router.beforeEach((to, from, next) => {
/* must call `next` */
})
router.beforeResolve((to, from, next) => {
/* must call `next` */
})
router.afterEach((to, from) => {})
Here is a documentation
There is also a hook called 'beforeRouteEnter'.
Link to beforeRouteEnter docs

Accessing app inside beforeRouteEnter

I'd like to show some loading animation in the app root while a component prepares to be rendered by vue router.
Already found this question, proposing the use of navigation guards, and another question, where the accepted answer shows how to use the beforeEach guard to set a variable in app, showing a loading animation.
The problem is that this doesn't work when deep-linking to some route (initial url includes a route path, such as 'someurl#/foo'). The beforeEach guard simply doesn't get called then.
So i switched to the loaded component's beforeRouteEnter guard, which would also allow me to show the loading animation for some components only:
app:
var app = new Vue({
el: '#app',
data: { loading: false }
router: router
});
component:
var Foo = {
template: '<div>bar</div>',
beforeRouteEnter: function(to, from, next) {
app.loading = true; // 'app' unavailable when deep-linking
// do some loading here before calling next()...
next();
}
}
But then i found that when deep-linking to the component, app isn't available in beforeRouteEnter, as it gets called very early in the initialisation process.
I don't want to set loading to true inside the app data declaration, as i might decide at some point to deep-link to another route, whose component doesn't need a loading animation.
I believe, your solution is correct. However, I would suggest using next() function instead. As written in vue-router docs.
https://router.vuejs.org/en/advanced/navigation-guards.html
The beforeRouteEnter guard does NOT have access to this, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
However, you can access the instance by passing a callback to next. The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument:
beforeRouteEnter (to, from, next) {
next(vm => {
vm.$root.loading = true;
})
}
Found a workaround using Vue.nextTick:
beforeRouteEnter: function(to, from, next) {
Vue.nextTick(function(){
// now app is available
app.loading = true;
// some loading to happen here...
seTimeout(function(){
app.loading = false;
next();
}, 1000);
})
}
Feels a little hacky, so would be thankful for other suggestions.
Find a demo of this solution here:
https://s.codepen.io/schellmax/debug/aYvXqx/GnrnbVPBXezr#/foo
What about using beforeRouteLeave to trigger the loading then have the component toggle it off in mounted.
For the initial load of the app you could have
app:
var app = new Vue({
el: '#app',
data() => ({ loading: true }),
mounted() { this.loading: false },
router: router
});
then for your components
component:
var Foo = {
template: '<div>bar</div>',
mounted() {
app.loading = false;
},
beforeRouteLeave(to, from , next) {
switch(to){
case COMPONENT_TO_SHOW_LOADING_ON:
case OTHER_COMPONENT:
app.loading = true;
default:
}
}
}

In vue-router, how to use beforeLeave

I want to finish some information verify before one component leave.
I've scanned the vue-router document: https://router.vuejs.org
But I use vue-cli, in my file: router1.vue,
console.log(this.$router.beforeLeave) -> undefined
How can I use it?
Add this to your router1.vue:
export default {
//...
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.
},
//...
}
for example:
beforeRouteLeave (to, from , next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
And it will be called before this route leave.
ref: https://router.vuejs.org/en/advanced/navigation-guards.html