Description
What problem does this feature solve?
Adds possibility to handle aborted navigations globally.
Initiated vue-router navigation could have different outcomes:
- successful navigation
- errored navigation (in case there is non-internal js error in one of the guards or hooks)
- aborted navigation
In cases, when application logic depends on these navigation results, vue-router
has very limited support to catch aborted navigations, and no support to do it globally.
Successful navigation can be catched in global afterEach
hook or in beforeResolve
guard (however error still can be thrown there). Also there is optional onComplete
callback for router.push
, but there is no way to set it from <router-link>
.
Navigation with error (because of JS error in one of the guards or hooks) can be catched in global onError
hook. Also there is optional onAbort
callback for router.push
(which will be called for both navigation with error and aborted navigation), but there is no way to set it from <router-link>
.
When it comes to aborted navigations, the only place where it can be catched – onAbort
callback for router.push
. However, as mentioned before, it is not possible to set it from <router-link>
. Moreover, there is no global hook to catch aborted navigations at all.
Use case
In our application we would like to simply display loading indicator in the root App component while navigation is in progress. Also, we would like to have this functionality be generic, so it can be applied to different applications. The easiest way to achieve it is by creating separate ES module like this navigation-state-plugin.js
:
import Vue from 'vue';
export const state = Vue.observable({ navigating: false });
export default router => {
// When navigation initiated – update state and proceed
router.beforeEach((to, from, next) {
state.navigating = true;
next();
});
// When navigation successfully finished – update state accordingly
router.afterEach(() => {
state.navigating = false;
});
// When navigation failed –also update state
router.onError(error => {
state.navigating = false;
});
};
In this way in can be applied in the main.js
:
import VueRouter from 'vue-router';
import applyPlugin from './navigation-state-plugin.js`;
// do other initializations
const router = new VueRouter(...);
applyPlugin(router);
// init application
Also state from this file can be imported in App.vue and used to display loading indicator when state.navigating
is true
.
However, when navigation is aborted there is no way to track it in a global manner, which leads to loading indicator to stay visible on a screen.
As possible solution, it was suggested to "use router.push
or the v-slot api and get the promise returned from navigate
" (which is currently not available because #3042 has no PR). However this solution would require to create wrappers around vue-router API, which should always be used throughout entire application instead of native vue-router
api. Even though it is easy to do for router.push
, it is cumbersome to do for <router-link>
because wrapper should be able to support the same rendering scenarios (without slot content, with slot content, with scoped slot content, etc.)
It is also not suitable for us, because we have shared set of router components, which are re-used in different vue applications. And not all applications will have navigation-state-plugin.js
. So we need to have possibility to use native router.push
and <router-link>
with an option to globally manage navigation state.
What does the proposed API look like?
Most obvious would be to introduce global onAbort
hook:
router.onAbort(function callback(abortedRoute, reason) { ... });
where reason
can be one of the errors from #3047
It may be possible to call existing onError
or afterEach
hooks also for aborted navigations, but that would be a breaking change, and probably would make API more confusing.