Detect Back Button in Navigation Guards of Vue-Router
Solution 1
This is the only way that I've found:
We can listen for popstate, save it in a variable, and then check that variable
// This listener will execute before router.beforeEach only if registered
// before vue-router is registered with Vue.use(VueRouter)
window.popStateDetected = false
window.addEventListener('popstate', () => {
window.popStateDetected = true
})
router.beforeEach((to, from, next) => {
const IsItABackButton = window.popStateDetected
window.popStateDetected = false
if (IsItABackButton && from.meta.someLogica) {
next(false)
return ''
}
next()
})
Solution 2
As stated by @Yuci, all the router hook callbacks are performed before popstate is updated (and therefore not helpful for this use case)
What you can do:
methods: {
navigate(location) {
this.internalNavigation = true;
this.$router.push(location, function () {
this.internalNavigation = false;
}.bind(this));
}
}
- Wrap 'router.push' with you own 'navigate' function
- Before calling router.push, set 'internalNavigation' flag to true
- Use vue router 'oncomplete' callback to set internalNavigation flag back to false
Now you can check the flag from within beforeEach callback and handle it accordingly.
router.beforeEach((to, from, next) => {
if ( this.internalNavigation ) {
//Do your stufff
}
next()
})
Solution 3
Slight improvement to @yair-levy answer.
Wrapping push
to own navigate
method is not convenient because you usually want to call push
() from various places. Instead, router original methods can be patched in one place without changes in remaining code.
Following code is my Nuxt plugin to prevent navigation triggered by back/forward buttons (used in Electron app to avoid back caused by mouse additional "back" button, which makes mess in Electron app) Same principle can be used for vanilla Vue and to track common back button together with your custom handling.
export default ({ app }, inject) => {
// this is Nuxt stuff, in vanilla Vue use just your router intances
const { router } = app
let programmatic = false
;(['push', 'replace', 'go', 'back', 'forward']).forEach(methodName => {
const method = router[methodName]
router[methodName] = (...args) => {
programmatic = true
method.apply(router, args)
}
})
router.beforeEach((to, from, next) => {
// name is null for initial load or page reload
if (from.name === null || programmatic) {
// triggered bu router.push/go/... call
// route as usual
next()
} else {
// triggered by user back/forward
// do not route
next(false)
}
programmatic = false // clear flag
})
}
Comments
-
Asqan almost 2 years
How the route is changed, matters for my case. So, I want to catch when the route is changed by a back button of browser or gsm.
This is what I have:
router.beforeEach((to, from, next) => { if ( /* IsItABackButton && */ from.meta.someLogica) { next(false) return '' } next() })
Is there some built-in solutions that I can use instead of
IsItABackButton
comment? Vue-router itself hasn't I guess but any workaround could also work here. Or would there be another way preferred to recognize it? -
Asqan over 5 yearsI was expecting a better solution from Navigation-Guards for this problem but probaby this is indeed currently the only thing what we can do about this problem.
-
Yuci over 4 years@Nuno Balbona Pérez, it looks like the popstate event handler runs AFTER the router.beforeEach callback. I don't think this would work just as is.
-
PirateApp over 3 yearsupvoted! where are you putting this code? inside router.js? i am using nuxt here
-
mix3d over 3 yearsCan you explain how this works to detect that a back button was detected? How to use this to solve the OP's written problem? How does the
scrollBehavior
event fire in relation tobeforeEach
? -
ego over 3 yearsTo take care of calling order problem It's possible to timeout or nextick-wrap beforeEach logic. Not elegant, but this solution works quite well. Bigger problem is that this solution also reacts to forward-btn click - but to be fair question doesn't say anything about it lol
-
Roland Pihlakas about 3 yearsYou can also add
to.meta.fromHistory = (savedPosition !== null);
if you need to let the rest of the app know it’s from back/forward.