I am trying to dynamically set the target component for a vue-router route.
The reason is that I need to retrieve data from the server to know the correct component to target.
One solution I thought about was to add a global guard similar to:
router.beforeEach((to, from, next) => {
get_data_from_server(to.fullPath).then(() => {
// set the "to" component, but how?
next();
})
But, as the comment says, how would I set "component" - there is no component property in the "to" object. (It looks like it is in components.default.render but that isn't documented and doesn't look like it should be messed with.)
Another alternative is to the use above logic but add special-case code to redirect but then I found there was no way to create a dynamic/new route that targeted the correct component.
How should one go about creating a route to a destination that is not known at build-time?
This solution seems to work.
router.beforeEach((to, from, next) => {
get_data_from_server(to.fullPath).then(component => {
if (to.matched.length === 0) {
// the target route doesn't exist yet
router.addRoutes({path: to.path, component: component})
next(to.fullPath)
return
} else {
// the route and component are known and data is available
next()
}
})
The key was finding the link in my comment above that shows the vue-router commit that implements addRoutes().
One thing to be aware of is that new routes are added at the end (makes sense) so any wildcards present might create unintended matches. I had a catchall route in place that redirected to a default page. But that matched before the newly added match because it was earlier the list. And that caused a stack overflow. In effect the code after if (to.matched.length === 0) is the default route.
Related
I have a nuxt one-pager website and I am trying to set up a router for the language switcher. My headless CMS (Storyblok) has language logic setup by using ?language=xyz.
The data is fetched by using AsyncData() hook in the pages/index.vue.
Now when I change the route from '/' to '/xyz' the AsyncData() is called again and the route changes.
When I do not change the path but only the query (language=xyz) this does not happen.
I tried to add the following:
beforeRouteUpdate (to, from, next) {
console.log('BEFORE UPDATE', to.query.language);
console.log('BEFORE UPDATE 2', this.$route.query.language);
if(to.query.language != this.$route.query.language) {
//this.$router.push({ path: '/', query: { language: to.query.language } })
next();
}
}
When I now click on the navigation I can see in the console log, the current and new language but still nothing happens.
When I try to push the route (commented out code above) I get an endless loop and see the beforeRouteUpdate running again and again, but the AsyncData is not run.
What can I do, so the behavior is the same as when the path of the route changes?
Are you sure that you are actually fetching it again from storyblok? Are you fetching it with the storyblok module or are you fetching it straight from the API manually?
What I would suggest as a counter option would be to add the nuxt i18n module and fetch the data from storyblok with the i18n language and use the i18n language switch feature! That way you can also add translations that don't fit into the storyblok structure.
I have two (or more) VueJS routes that contain a router-link pointing to the same third route.
/pages/list
<router-link :to="{name:'add-page'}">Add</router-link>
/pages/other-list
<router-link :to="{name:'add-page'}">Add</router-link>
If I put a put link on the third page with $router.back(), the link will take me back in history to whichever previous page I came from.
However, if enter the add-page route directly into my browser, there is no history. I would like to set a default value for that page, and preferably not rely on a global default.
What is the best way to select a default route if there is no history on a specific component?
Would a Navigation Guard fits your needs?
You would use it something like:
router.beforeEach((to, from, next) => {
if (to.name === 'add-page' && to.) {
//Your logic here
//eg a redirect
next({ name: 'MyBaseRoute' })
} else {
next()
}
})
or if you need just for a selected component you can use the in-component guard:
beforeRouteEnter (to, from, next) {
// called before the route that renders this component is confirmed.
// does NOT have access to `this` component instance,
// because it has not been created yet when this guard is called!
},
In Vue.js, I'm using <router-link> to navigate, as such:
<router-link :to="{ path: '/', query: { q: item.id, lang: lang } }">{{item.name}}</router-link>
This does not update the path, only the query string. Although the resulting URL is formatted correctly, it does not trigger navigation in Vue, apparently because the path has not changed, only the query string.
If I put a beforeRouteUpdate hook on my component, I can see that the new query parameters appear in the "to" object.
How can I make Vue perform the navigation, even though only the query parameters have changed?
You have most probably solved this already, so this answer could be useful for others with this problem.
Documentation link - In-Component Guards - beforeRouteUpdate
If I put a beforeRouteUpdate hook on my component, I can see that the new query parameters appear in the "to" object
This means you doing it all correctly since you are seeing the new parameters in the component guard, So next, what you need to do is to do as prescribed below in docs and example
Fetching After Navigation
So all you now have to do is fetch your new data using these new parameters
beforeRouteUpdate (to, from, next) {
this.post = null
// replace `getItem` with your data fetching util / API wrapper
getItem(to.params.q, to.params.lang, (err, post) => {
this.post = post
next()
})
}
I'm seeing some behaviour I don't understand in the beforeRouteEnter navigation guard with vue.js/vue-router. I understand from the docs that this guard "does NOT have access to this component instance", but that if you need to get access to the component instance you can do so by means of a callback. I've done this because I want to abort the route change if one of the props hasn't been defined (normally because of a user clicking a forward button). So this is what I have:
beforeRouteEnter(to, from, next) {
console.log("ProductDetail: routing from = "+from.path+" to "+to.path);
next(vm => {
if (!vm.product) {
console.log("Product empty: routing back one page");
vm.$router.go(-1);
}
});
},
The idea is that I test for the existence of the prop and if it's not valid, go back (or otherwise abort the route change). From the console log, I can see that what is happening, though, is that the component instance is in fact getting created, presumably as a result of the callback being called, and throwing a bunch of errors, before the vm.$router.go(-1) kicks in and takes the user back to the previous screen.
So what, if anything, can I do to actually prevent the route change from completing if one of the requisite conditions isn't present, if it's too late by the time I can test for it?
You can try this code
beforeRouteEnter(to, from, next) {
// Your code
next(vm => {
if (!vm.product) {
console.log("Product empty: routing back one page");
next(from)
}
});
}
You can read more about this guard in https://router.vuejs.org/en/advanced/navigation-guards.html
Have you tried: next(false)?
next(false): abort the current navigation. If the browser URL was changed (either manually by the user or via back button), it will be reset to that of the from route.
Reference
I have configured some basic routes that are available for all users before they log in:
App.config(function ($routeProvider) {
$routeProvider.
when('/login', { templateUrl: 'views/login.html', controller: PageStartCtrl.Controller }).
otherwise({ redirectTo: '/login' });
});
So the only thing user can do is to log in. After the user logs in, I would like to register additional routes like this:
$http
.post('api/Users/Login', { User: userName, Password: userPassword })
.success(function (response : any) {
App.config(function ($routeProvider) {
$routeProvider
.when('/dashboard',
{ templateUrl: 'part/dashboard.html',
controller: DashboardCtrl.Controller });
});
However, I suppose I should call .config method only once, because the $routeProvider is brand new instance that knows nothing about /login route. Further debugging showed me that the first instance of $resourceProvider is used when resolving view change.
Q: Is there a way how to register routes later?
Solution from Add routes and templates dynamically to $routeProvider might work, but is quite ugly (involved global variable nastyGlobalReferenceToRouteProvider).
Since routes are defined on a provider level, normally new routes can only be defined in the configuration block. The trouble is that in the configuration block all the vital services are still undefined (most notably $http). So, on the surface it looks like w can't define routes dynamically.
Now, it turns out that in practice it is quite easy to add / remove routes at any point of the application life-cycle! Looking at the $route source code we can see that all the routes definition are simply kept in the $route.routes hash which can be modified at any point in time like so (simplified example):
myApp.controller('MyCtrl', function($scope, $route) {
$scope.defineRoute = function() {
$route.routes['/dynamic'] = {templateUrl: 'dynamic.tpl.html'};
};
});
Here is the jsFiddle that demonstrates this in action: http://jsfiddle.net/4zwdf/6/
In reality, if we want to be close to what AngularJS is doing the route definition logic should be a bit more complex as AngularJS is also defining a redirect route to correctly handle routes with / at the end (make it effectively optional).
So, while the above technique will work, we need to note the following:
This technique depends on the internal implementation and might break if the AngularJS team decides to change the way routes are defined / matched.
It is also possible to define the otherwise route using the $route.routes as the default route is stored in the same hash under the null key
I found that the answer by #pkozlowski.opensource works only in angularjs 1.0.1. However, after angular-route.js becomes an independent file in the later version, directly set the $route doesn't work.
After reviewing the code, I find the key of $route.routes is no longer used to match location but $route.route[key].RegExp is used instead. After I copy the origin when and pathRegExp function, route works. See jsfiddle:
http://jsfiddle.net/5FUQa/1/
function addRoute(path, route) {
//slightly modified 'when' function in angular-route.js
}
addRoute('/dynamic', {
templateUrl: 'dynamic.tpl.html'
});