I am using the same vue-component to display data that I'm fetching with axios. After clicking on a link the ajax-request is fetching new data.
But if the component is already loaded and the user clicks another link to load different data, the next()-method is not called.
Check out my codepen to see what I mean. There should be an alert showing. If you click a link for the first time, it does. If you switch to another link, it does not.
https://codepen.io/spqrinc/pen/YzXGGGL
From the docs:
Note that beforeRouteEnter is the only guard that supports passing a callback to next. For beforeRouteUpdate and beforeRouteLeave, this is already available, so passing a callback is unnecessary and therefore not supported
So the next() in beforeRouteUpdate can't take a callback because you already have access to the instance in this. Anything you would have done with vm in that callback can be done without it using this:
beforeRouteUpdate(to, from, next) {
alert("Reloaded");
// `this` works
next();
}
In VueJS, I am showing the loader on each route as:
router.beforeEach((to, from, next) => {
store.commit('loading', true);
next();
})
But if server loads the page in less than one second then it looks weird to show loader for this request, for just one sec.
What I want to wait for some time let just say 2sec or maybe 3sec and after all, if the page is not loaded yet then show loader otherwise not. So for this, I put setTimeout as:
router.beforeEach((to, from, next) => {
setTimeout(() => {
store.commit('loading', true);
}, 500);
next();
})
Now the loader is always shown never goes then I also tried to move next() statement into setTimeout but then the page first waits for 500 mili-sec then the loader shows up and then hides suddenly and page loads.
I want to make it in a better way, any suggestions?
I think you are not understanding the Vue Router Navigation Guards. According to Vue Router Docs:
Global before guards are called in creation order, whenever a navigation is triggered. Guards may be resolved asynchronously, and the navigation is considered pending before all hooks have been resolved.
In simple words, just show the loader in beforeEach as you are doing:
store.commit('loading', true);
And just hide it in afterEach as:
store.commit('loading', false);
That's it.
Don't add setTimeout in afterEach.
Hope it will help.
so about your question. As of now you're only delaying commiting 'loading' mutation by 500ms. To answer your question you should do something like that:
router.beforeEach((to, from, next) => {
store.commit('loading', true);
setTimeout(() => {
store.commit('loading', false);
}, 500);
next();
})
That will delay commiting store.commit('loading', false); by 500ms. The question is do you really want to falsely delay loading of a component. Why not use transitions in that case ?
Here is example how loading next route is delayed
https://codesandbox.io/s/vue-highcharts-demo-nn3uv
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?
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()
})
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
})
}
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()
})
I found a simple way to solve this after spending a lot of time trying to refine the codes to work well in my case and without a glitch.
export const handleBackButton = () => {
router.beforeEach((to, from, next) => {
if (window.event.type == 'popstate' && from.name == 'HomePage'){
next(false);
}else{
next();
}
});
}
The window.event.type == 'popstate' checks if the back button is pressed
And from.name == 'HomePage' checks the page on which the back button is pressed or you are routing from.
HomePage as the name where you want to disable back button. You can leave this condition if you want to disable it throughout the site.
next(false) and next() to stop or allow navigation respectively.
You can place the code in a navigationGuard.js file and import it to your main.js file
I tried other methods, including calling from the components but it produces a glitch and the rerouting becomes obvious. But this leaves no glitch at all.
Hope this works for you. Cheers
I had the same problem regarding detecting Back Button navigation as opposed to other types of navigation in my Vue App.
What I ended up doing was adding a hash to my real internal App navigation to differentiate between intended App navigation and Back Button navigation.
For example, on this route /page1 I want to catch Back Button navigations to close models that are open. Imagine I really wanted to navigate to another route, I'll add a hash to that route: /page2#force
beforeRouteLeave(to, from, next) {
// if no hash then handle back button
if (!to.hash) {
handleBackButton();
next(false); // this stops the navigation
return;
}
next(); // otherwise navigate
}
This is rather simplistic, but it works. You'll want to check what the hash actually contains if you use them for more than this in your app.
performance.navigation is deprecated so whatch out! https://developer.mozilla.org/en-US/docs/Web/API/Performance/navigation
When you want to register any global event listener you should be very careful with that. It will be called each time since registration moment untill you unregister that manualy. For me the case was that I have register popstate listener when component was created to listen and call some action when:
browser back button
alt + arrow back
back button in mouse
was clicked. After that I have unregister popstate listener to not call it in other components where I don't want it to be called, keep Your code and method calls clean :).
My code sample:
created() {
window.addEventListener('popstate', this.popstateEventAction );
},
methods: {
popstateEventAction() {
// ... some action triggered when the back button is clicked
this.removePopstateEventAction();
},
removePopstateEventAction() {
window.removeEventListener('popstate', this.popstateEventAction);
}
}
Best regards!
The accepted answer almost worked for me, but I found that the listener was behind by 1 click, probably due to the issue that #Yuci highlighted.
The answer from #farincz worked best for me, but since it wasn't written for vanilla Vue, I thought I'd write down what worked for me here:
// after createRouter
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(async (to, from) => {
if(!from.name === null || !programmatic) {
// do stuff you want to do when hitting back/forward or reloading page
}
programmatic = false // clear flag
});
This is done very easily.
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
// History back position, if user click Back button
return savedPosition
} else {
// Scroll to top of page if the user didn't press the back button
return { x: 0, y: 0 }
}
}
})
Check here:
https://router.vuejs.org/guide/advanced/scroll-behavior.html#async-scrolling
I'm making a JHipster project and I need to show a different home page for each role that I log in with, I'm using Angular 1.x.
For example I have the ROLE_ADMINand the ROLE_USERand I need to show a different dashboard for each on.
I have read that I can put something like this in the home.controller.js
this.eventManager.subscribe('authenticationSuccess', (message) => {
this.principal.identity().then((account) => {
if (account.authorities.indexOf("ROLE_ADMIN") >=0)
{
this.router.navigate(['#/pages/prueba/prueba.html']);
}
else
{
this.account = account;
}
});
});
But I can't make it work, it shows this error: Error: this is undefined
Anyone have a clue about this?
You can have a look at auth.service.js. There is a method called authorize, which in turn calls authThen. These methods are invoked after the user is authenticated and normally redirects the user to the last state (normally the protected state that failed, since the user was not authenticated and therefore was redirected to the login). You may change the code here to redirect the user according to its authorities.
The same methods (authorize and authThen) are also called everytime before a state changes, because it is a "resolve" for each state (have a look at the app.state.js).
Another option would be to add an "onEnter" function to your state definition that redirects to the appropiate view.
When a user is about to leave the present route, I'd like to display a warning and let him choose between leaving (and losing changes) and staying in the current route.
In order to catch all possible transitions, I need to do this in the routes willTransition method.
I'm attempting to abort() and then retry() the transition if the user chooses to. But the retry doesn't seem to have any effect. It should be noted that it is called asynchronously. Here's a twiddle that demonstrates that: https://ember-twiddle.com/b6d8ddb665ff79f2988277912916e77b?openFiles=routes.my-route.js%2C
Here's my route example route:
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
willTransition(transition) {
transition.abort();
Ember.run.later(function(){
console.log('Transition... now!');
transition.retry();
}, 2000);
return true;
}
}
});
The log shows up, but I never get redirected to the application route.
Take a look into log. You will see that "Transition... now!" appears there every 2 sec. That shows that willTransition works again and again. So you need some flag that allows you to go away. Updated twiddle
When a user is about to leave the present route, I'd like to display a warning and let him choose between leaving (and losing changes) and staying in the current route.
For the above requirement, the below code is enough.
willTransition(transition) {
if (!confirm('Are you sure to navigate ?')) {
transition.abort();
return false;
}
return true;
}