-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add possibility to handle aborted navigations globally #3157
Comments
I was working on an RFC to address a few problems and one of them is the one you are facing. The RFC is here vuejs/rfcs#150. Your input would be helpful 🙂 let's move the discussion there but I don't think it makes sense to add a new api to remove it right away. What you need can be implemented in user land by overriding const onAborts = []
const push = Router.prototype.push
Router.prototype.push = function () {
return push.apply(router, arguments).catch(err => {
// trigger onAbort callbacks
return Promise.reject(err)
})
} The behavior can be extended to reflect a similar behavior to the one discussed on the RFC |
@posva Thanks for your reply and proposed solution. Happy to see RFC to align those guards and callbacks in behavior. As for proposed workaround, even though it works in some basic cases, unfortunately it doesn't cover all use cases. Let me explain. Here is what I initially made to extend VueRouter api (based on your suggestion): import VueRouter from 'vue-router';
const abortCallbacks = [];
const processAbortCallbacks = () => {
abortCallbacks.forEach(callback => {
callback();
});
};
const getExtendedFunction = originalFn => function (...args) {
// If more than 1 argument - method called with callbacks
if (args.length > 1) {
const [location, onComplete, onAbort] = args;
return originalFn.call(this, location, onComplete, () => {
processAbortCallbacks();
onAbort && onAbort();
});
}
// Otherwise it will return promise
return originalFn.apply(this, args)
.catch(error => {
processAbortCallbacks();
return Promise.reject(error);
});
}
const routerPush = VueRouter.prototype.push;
const routerReplace = VueRouter.prototype.replace;
VueRouter.prototype.push = getExtendedFunction(routerPush);
VueRouter.prototype.replace = getExtendedFunction(routerReplace);
VueRouter.prototype.onAbort = callback => {
abortCallbacks.push(callback);
}; Note that I added support for As I mentioned, this only works for some simplest cases. Consider this routing setup: new VueRouter({
routes: [
{ path: '/home' },
{ path: '/redirect', beforeEnter(to, from, next) { next('/home') },
{ path: '/indirect-redirect', beforeEnter(to, from, next) { next('/redirect') },
],
}); Supported use case: When user is on Unsupported use case: When user is on This issue is not related to the RFC itself, so I posted it here. Do you think it make sense to re-open this issue, or should I move this reply to the RFC? |
The thing is the behavior you are describing about Note this is different from the point your raised in the RFC, which is valid |
@posva Don't get me wrong, I understand that I can try to do the following (which theoretically should work, didn't try yet): export default function applyRouterShim (router) {
const originalHistoryPush = router.history.constructor.prototype.push;
router.history.constructor.prototype.push = function extendedPush(location, onComplete, onAbort) {
return originalHistoryPush.call(this, location, onComplete, error => {
// ...process abort callbacks
onAbort && onAbort(error);
});
}
} However I'm not fond of this solution as it is rather "fragile" and based on private, un-documented api ( |
I don't understand why that's a problem, that would call |
The problem that |
@posva Here it is: https://codesandbox.io/s/still-sun-p4tmn?expanddevtools=1 Upon loading, user is on the Home ( Router initialization is in Scenario 1 (ok): Expected: In the Console will be printed:
Text on the screen should stay as "Navigating: false", view name is visible. Result: Same as expected Scenario 2 (not ok): Expected: In the Console will be printed:
Text on the screen should stay as "Navigating: false", view name is visible. Result: In the Console will be printed:
Text on the screen will change to "Navigating: true", view name is not visible. In real-life app this would result in showing loading indicator instead of actual View. |
Ah, I see what you mean now, thanks a lot for the detailed explanation. The expected behavior would be
And that's how it should be on the next version vue-router (with |
Happy to hear it example made it clear.
What about introduction of the Btw, I'm not sure what is the guideline you have for VueRouter issues, but does it make sense to re-open this ticket since it is still an issue for current latest version? |
Introducing an api to remove it right away is, for me, worse that the breaking change because it's likely to create more confusion than the breaking change itself: returned Promise by |
I understand your point. I would try to override |
@bponomarenko can u provide the code that u ended up with? I mean any real copy-paste examples. |
@afwn90cj93201nixr2e1re This is what I ended up doing: https://gist.github.com/bponomarenko/253c64d355373b8e7afccc11581b240a |
thank's, waited for that solution for one year... |
@afwn90cj93201nixr2e1re I do have Twitter, but I rarely post anything there. Which reminds me that this custom solution is worth of sharing, thanks for that ;) I have moved it to the public gist, so it would be easier to "spread the knowledge". |
I mean our position and development style, solutions and etc. are same, that's why i asked for tg (tbh im talking about messenger's especially), i can provide a lot of my solution's coz i faced with that problems and solved them in 2019. Ok, whatever, thanks anyway. |
Oh, I see. Unfortunately, I don't use tg. Maybe in the future ;) |
What problem does this feature solve?
Adds possibility to handle aborted navigations globally.
Initiated vue-router navigation could have different outcomes:
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 inbeforeResolve
guard (however error still can be thrown there). Also there is optionalonComplete
callback forrouter.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 optionalonAbort
callback forrouter.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 forrouter.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
:In this way in can be applied in the
main.js
:Also state from this file can be imported in App.vue and used to display loading indicator when
state.navigating
istrue
.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 fromnavigate
" (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 nativevue-router
api. Even though it is easy to do forrouter.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 nativerouter.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:where
reason
can be one of the errors from #3047It may be possible to call existing
onError
orafterEach
hooks also for aborted navigations, but that would be a breaking change, and probably would make API more confusing.The text was updated successfully, but these errors were encountered: