Skip to content

global beforeResolve but for programmatic matcher.resolve use #2510

Closed
@4storia

Description

@4storia

What problem is this solving

I'm pretty sure this isn't possible today, but I'd be curious if I'm thinking about this problem correctly, and if so would love to propose extending the existing resolver functionality. Here's the gist:

Our application has a ton of routes, most of which fall into a single parent that looks something like /foo/:namespace. When navigating between these routes, we like to assume we already have access to the :namespace param, and omit it from our route objects. For instance, if we need to go to /foo/:namespace/stats, which has route name StatsPage, we'll call $router.push({ name: 'StatsPage' }), omitting the params: { namespace: '123' }. It's worth noting that a user only ever interfaces with the app in the context of a single namespace at a time, so we can essentially always assume the namespace is whatever the users currently active namespace is.
However, not all of our routes fit this pattern. For instance, if we have several routes that end up looking like /global/thing. In these cases, the above $router.push({ name: 'StatsPage' }) needs to have a resolve hook that injects the namespace value for the user. This all works totally fine today when navigating programmatically via router pushes, because global/route layer beforeResolve hooks can inject what we need.
The problem comes in when we need to generate clickable hrefs - $router.resolve doesn't call any custom resolvers, meaning if we're on route /global, any generated hrefs to namespaced routes throw errors because they can't resolve :namespace. Effectively, this means for anything that might live on a global page that also needs to produce an href outside of a <router-link>, we can't take shortcuts with assuming the :namespace.

Proposed solution

Obviously there are lots of ways to solve this in userland, but I've noticed it can also be solved via this little hack of overloading the router's native resolve:

const original = router.resolve;

router.resolve = (to, currentLocation = {}) => {
    to.params = {
        ...to.params,
        namespace: currentLocation.params?.namespace || store.state.namespace.id
    };

    return original(to, currentLocation);
}

This is effectively just a beforeResolve hook, but applied to the routers resolve method directly, vs only being applied at time of navigation. This allows us to generate links from anywhere in the codebase by directly calling the routers standard api $router.resolve, vs needing to have a helper function/composable/etc that devs need to know to use whenever they generate an href.

I'm positive blindly saying "also apply the global beforeResolve hook to matcher.resolve" would result in a ton of problems, but it would be awesome to have something like:

router.matcherBeforeResolve = (to, currentLocaiton) => {
  // return modified object that gets passed to `router.resolve(to, currentLocation)`
  return {
    ..to,
    params: {
      ...to.params,
      new: 'param'
    }
};

This would run directly before the matcher attempts to resolve to a route, passing the output of the function to the normal router.resolve. For what its worth, assuming this isn't already possible, and makes sense to add, I'm happy to put up a draft PR to get this going

Describe alternatives you've considered

Today we solve this problem via a composable, but having to pull a compsable in all over the place just to do this feels kinda goofy and results in a lot of duplication, while also being more "app specific knowledge" a dev needs to understand about the system

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions