To deal with something before start router and after the App.vue has mounted.
router.start()
Why would you need to start the router after the Vue app.
Usually, you can delay thing by starting the Vue app after
I don't think we will support this unless there is a valid and properly elaborated use case.
I was hoping for this feature as well.
I have the following use case. I authenticate users with OAuth(Laravel Passport) and I want to store the authorization token in a cookie, so users don't need to login on every page refresh or after opening a new tab. If the cookie was found, I want to delay the start of Vue Router until the authorization token has been confirmed to be valid. The process of validation can take several hundred milliseconds and it looks weird if I suddenly redirect from the login page to the app.
How would you approach this with the current API?
Should I make a global navigation guard and inside it redirect according to the cookie value?
import store from './path/to/store';
router.beforeEach((to, from, next) => {
if(store.getters.signedIn) {
// User has already been authenticated.
// Delegate the redirect logic to the route-specific navigation guards.
return next();
}
store.dispatch('try_to_authenticate_with_cookie')
.then(() => next('/app'), () => next('/login'))
})
I just realized, I can perform the cookie authentication before initializing the root Vue instance. It's quite ugly but it works:
function initializeApp() {
new Vue({
el: '#app',
store,
router,
})
}
let token = Cookies.get('my-app-authorization-token');
if (token) {
store.commit(mutationTypes.LOGIN_USER, token);
store.dispatch('getCurrentUser')
.then(
() => initializeApp(),
() => initializeApp()
);
} else {
initializeApp();
}
If the token was found in cookies, mutationTypes.LOGIN_USER modifies default axios headers to always include Authorization, then getCurrentUser fetches the signed in user or responds with 401 if the token was invalid.
If the token was not found, just proceed with unauthenticated user.
After that, route-specific navigation guards can access the signedIn getter on the store and redirect accordingly.
I'm struggling with the same issue. Of course, it's possible to find workarounds but none of them feels right.
My suggestion for this topic: Start the route events when router-view is mounted. This way we could manage the initialization inside vue and vuex easily. When everything is fine, we could just flip a flag in vuex and the routing process could start.
@ignaczistvan can you post an example?
Of course.
Let's take firebase authentication, where firebase does the auth process asynchronously and notifies me via an event emitter (onAuthstatechanged). Now I have two options:
It'd be nice if my Vue app could init itself and communicate with the router through vuex store bindings. Just like with other components.
Of course this raises a lot more questions about lifecycle management :) However, I think this could be a great direction if you'd like to support reactivity.
@KKSzymanowski I've tried your example but the Router is immediately there after import and the Router-Guard too because it's imported in the Router. So the dumb Router-Guard is checking the userAuth even if the App itself isn't initialized and the Store is still fetching the data from remote API. Very frustrating.
This is the workaround I use.
new Vue({
router: (() => {
let router = new VueRouter(/*...*/);
let promise = new Promise(resolve => {
router.start = resolve;
});
router.beforeEach(async (to, from, next) => {
await promise; // wait until 'start' called
next();
});
return router;
})(),
async created() {
// make some requests and set initial data
this.$router.start();
},
});
I'm using Vuex and Vue-router and this is the solution I've came up with. (Files trimmed down for brevity)
store.js:
export default new Vuex.Store({
state: {
initialized: false,
user: null,
},
mutations: {
initialize(state) {
state.initialized = true;
},
login(state, { user }) {
state.user = user;
},
},
actions: {
initialize(context) {
return context.dispatch('loadCurrentUser').then(() => {
context.commit('initialize');
});
},
loadCurrentUser(context) {
// getCurrentUser() is just for example here. Anything that returns a promise will work.
return getCurrentUser().then(user => {
context.commit('login', { user });
});
},
},
});
router.js
import store from './store';
const router = new Router(............);
router.beforeEach((to, from, next) => {
const checkAuth = () => {
const { user } = store.state;
// Guard logic here.....
};
if (!store.state.initialized) {
store.dispatch('initialize').then(() => {
checkAuth();
});
} else {
checkAuth();
}
});
export default router;
In my app template, I can use a computed value to get the store's initialized value and display a spinner & hide the app until initialization is complete.
I'm sure there's probably some drawbacks to the way I'm doing this, but it seems to be working so far.
@posva @yyx990803 guys, the problem is still actual and it would be nice to see your solution here.
We need to delay beforeEach call until we fetch user data from the server.
If _the problem_ is authentication with OAuth strategies, it's totally feasible with current behavior. You can still delay global guards after mounting and such. If you have an elaborated feature request with proper use cases I will take a look.
Locking as this thread is outdated
Remember to use the forum or the Discord chat to ask questions!
Most helpful comment
This is the workaround I use.