How to match a vue-router route and use it in a component function - vue.js

Using the vue-router package, it is easy to match dynamic segments:
router.map({
'/user/:username': {
component: {
template: '<p>username is {{$route.params.username}}</p>'
}
}
})
However, I couldn't figure out how to use the value a component method, e.g.
router.map({
'/user/:username': {
component: {
ready: function() {
// How to access the param here?
alert($route.params.username);
}
}
}
})
How can I access the matched segment in the component method?

Is almost as what you posted
// inside your component
this.$route.params.username
The key is that in the template you don't need to refer to the this scope, but in methods you have to use it

The $route object is injected in every router maped compeonents , as per your case you would use the params method to get the key/value passed .
router.map({
'/user/:username': {
component: {
ready: function() {
// How to access the param
// use the scope 'this'
alert(this.$route.params.username);
}
}
}
})
for more info check out http://router.vuejs.org/en/api/route-object.html

Related

How to initialize data with computed value inside asyncData?

I am building a web app with nuxt.
here's simplified code:
pages/index.vue
data() {
return {
item: {name:'', department: '', testField: '',},
}
}
async asyncData() {
const result = call some API
const dataToInitialize = {
name: result.username,
department: result.department,
testField: //want to assign computed value
}
return {item: dataToInitialize}
}
Inside asyncData, I call API and assign value to dataToInitialize.
dataToInitialize has testField field, and I want to assign some computed value based on username and department.
(for example, 'a' if name starts with 'a' and department is 'management'..etc there's more complicated logic in real scenario)
I have tried to use computed property , but I realized that asyncData cannnot access computed.
Does anyone know how to solve this?
Any help would be appreciated!
=======
not sure if it's right way, but I solved the issue by setting 'testfield' inside created.
created() {
this.item.testField = this.someMethod(this.item);
},
Looking at the Nuxt lifecyle, you can see that asyncData is called before even a Vue instance is mounted on your page.
Meanwhile, fetch() hook is called after. This is non-blocking but more flexible in a lot of ways.
An alternative using fetch() would look like this
<script>
export default {
data() {
return {
staticVariable: 'google',
}
},
async fetch() {
await this.$axios(this.computedVariable)
},
computed: {
computedVariable() {
return `www.${this.staticVariable}.com`
},
},
}
</script>
Another alternative, would be to use URL query string or params, thanks to Vue-router and use those to build your API call (in an asyncData hook).
Here is an example on how to achieve this: https://stackoverflow.com/a/68112290/8816585
EDIT after comment question
You can totally use a computed inside of a fetch() hook indeed. Here is an example on how to achieve this
<script>
export default {
data() {
return {
test: 'test',
}
},
async fetch() {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${this.nice}`)
console.log(await response.json())
},
computed: {
nice() {
return this.test + 'wow!'
},
},
}
</script>
I found that destructuring fetch({}) causes issues with accessing this inside fetch scope ->
async fetch({ store, $anyOtherGlobalVar }){
store.dispatch...
// destructuring approach changes the scope of the function and `this` does not have access to data, computed and e.t.c
}
If you want to access this scope for example this.data, avoid destructuring and access everything through this.
async fetch() {
this.$store...
this.data...
}

Vue Test Utils - Skip created hook

I want to skip all of the methods that are being called within the created() hook. Is there a way to do this?
So instead of this
created() {
this.getAllocations();
this.getModels();
this.getTeams();
this.getCustodians();
this.getDefaultFeeStructure();
}
I want this
created() { }
It's worth noting, I cannot actually change the component itself, but for testing purposes, this needs to be done.
You can accomplish this with a global mixin (see https://v2.vuejs.org/v2/guide/mixins.html#Global-Mixin)
However, for your case you need a custom merge strategy to prevent the created hook on the component from being run:
Hook functions with the same name are merged into an array so that all of them will be called. Mixin hooks will be called before the component’s own hooks. (https://v2.vuejs.org/v2/guide/mixins.html#Option-Merging)
See a working example at https://jsfiddle.net/rushimusmaximus/9akf641z/3/
Vue.mixin({
created() {
console.log("created() in global mixin")
}
});
const mergeCreatedStrategy = Vue.config.optionMergeStrategies.created;
Vue.config.optionMergeStrategies.created = (parent, child) => {
return mergeCreatedStrategy(parent);
};
new Vue ({
el: "#vue-app",
template: '<p>See console output for logging. Rendered at {{renderDate}}</p>',
data() {
return {
renderDate: new Date()
}
},
created() {
console.log("created() in component")
}
})

dynamic importing components based on route

I'm new to VueJS and I haven't found a possibility to load components based on route. For example:
page/:pageid
page/one
page/two
I have a component Page.vue
Within that component, I watch route changes. If the route is $pageid, then import and load component $pageid.
I've read this documentation: https://v2.vuejs.org/v2/guide/components-dynamic-async.html. But that's more focussed on lazy-loading. I don't see an example for dynamic importing and loading.
Regards, Peter
According to Dynamic Route Matching of vue router, you can access the url parameters via the params property of the $route object. In your case it would be $route.params.pageid so you can use it to dynamically change the content base on the pageid parameter in the url. Also note that on url change from say in your case page/one to page/two the same component would be used, so you would have to watch the $route object change and change your content dynamically.
watch: {
'$route' (to, from) {
// react to route changes...
}
}
Vue allows you to define your component as a factory function that
asynchronously resolves your component definition.
Since import() returns a promise, so you can register your async component by using:
export default {
components: {
'Alfa': () => import('#/components/Alfa'),
'Bravo': () => import('#/components/Bravo'),
'Charlie': () => import('#/components/Charlie')
}
}
Vue will only trigger the factory function when the component needs to
be rendered and will cache the result for future re-renders.
So your component will be load only when it need to be render.
And you can use dynamic component to render it by using:
<component :is='page'/>
and
export default {
computed: {
page () {
return 'Alfa'
}
}
}
If you already using vue-router you can directly use this in routes definition. See more in document here.
const router = new VueRouter({
routes: [{
path: '/alfa',
component: () => import('#/components/Alfa')
}, {
path: '/bravo',
component: () => import('#/components/Bravo')
}, {
path: '/charlie',
component: () => import('#/components/Charlie')
}]
})
As you can see this is dynamic importing but static registration (you have to provide the path to component.) which fit mostly in many situations. But if you want to use dynamic registration, you can return component directly instead of name see document here.
export default {
computed: {
page () {
return () => import('#/components/Alfa')
}
}
}

Component without template

I have a bit of code that makes an api call to a server and returns some JSON.
It did exist as a method in my component but as it is getting a bit long I want to extract it to it's own file
In vuejs what is the best practice here.
should it be a component without a template? How would this work?
will I just create an es6 module?
I would suggest using a mixin here.
In a file like myCoolMixin.js define your mixin...
export default {
methods: {
myAwesomeMethod() {
//do something cool...
}
}
}
You can define anything in a mixin just like a component. e.g. data object, computed or watched properties, etc. Then you simply include the mixin in your component.
import myCoolMixin from '../path/to/myCoolMixin.js'
export default {
mixins: [myCoolMixin],
data: function() {
return: {
//...
}
},
mounted: function() {
this.myAwesomeMethod(); // Use your method like this!
}
}
More on Mixins here: https://v2.vuejs.org/v2/guide/mixins.html
Mixins work, or you could create a plugin. Here's the docs example:
MyPlugin.install = function (Vue, options) {
// 1. add global method or property
Vue.myGlobalMethod = function () {
// something logic ...
}
// 2. add a global asset
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// something logic ...
}
...
})
// 3. inject some component options
Vue.mixin({
created: function () {
// something logic ...
}
...
})
// 4. add an instance method
Vue.prototype.$myMethod = function (methodOptions) {
// something logic ...
}
}
Vue Plugins

is it possible to specify which component should be used on router.go() in VueJS

In VueJS im trying to setup a scenario where the component used is determined by the url path without having to statically map it.
e.g.
router.beforeEach(({ to, next }) => {
FetchService.fetch(api_base+to.path)
.then((response) => {
router.app.$root.page = response
// I'd like to specify a path and component on the fly
// instead of having to map it
router.go({path: to.path, component: response.pageComponent})
})
.catch((err) => {
router.go({name: '404'})
})
})
Basically, I'd like to be able to create a route on the fly instead of statically specifying the path and component in the router.map
Hope that make sense. Any help would be appreciated.
I think that what you're trying to archive is programmatically load some component based on the current route.
I'm not sure if this is the recommended solution, but is what comes to my mind.
Create a DynamicLoader component whit a component as template
<template>
<component :is="CurrentComponent" />
</template>
Create a watch on $route to load new component in each route change
<script>
export default {
data() {
return {
CurrentComponent: undefined
}
},
watch: {
'$route' (to, from) {
let componentName = to.params.ComponentName;
this.CurrentComponent = require(`components/${componentName}`);
}
},
beforeMount() {
let componentName = this.$route.params.ComponentName;
this.CurrentComponent = require(`components/${componentName}`);
}
}
</script>
Register just this route on your router
{ path: '/:ComponentName', component: DynamicLoader }
In this example I'm assuming that all my componennt will be in components/ folder, in your example seems like you're calling an external service to get the real component location, that should work as well.
Let me know if this help you
As par the documentation of router.go, you either need path you want to redirect to or name of the route you want to redirect to. You don't the component.
Argument of router.go is either path in the form of:
{ path: '...' }
or name of route:
{
name: '...',
// params and query are optional
params: { ... },
query: { ... }
}
so you dont need to return component from your API, you can just return path or name of route, and use it to redirect to relevant page.
You can find more details here to create named routes using vue-router.