Get component using route param in routes.js - vue.js

I want the router to show a component based on the parameter in the path.
So currently my routes look like this:
const routes = [
{
path: "/pages/1",
component: () => import("pages/page-1.vue")
}
]
and I want them to look something like this:
const routes = [
{
path: "/pages/:page",
component: () => import("pages/page-{whatgoeshere?}.vue")
}
]

You could create just one page component DynamicPage.vue and then render the desired component there.
const routes = [
{
path: "/pages/:page",
component: () => import("pages/DynamicPage.vue")
}
]
DynamicPage.vue
<template>
<component :is="`page-${$route.params.page}`" />
</template>
<script>
// Import all page components
</script>

Related

Vue2 - change the route based on store value

I am trying to change the route of my components based on a value saved within the vuex store. There are four different template files, saved within different directories (template1, template2 etc). I am pulling the template id from the details store and depending on this value would like to render the component from the correct directory.
For example, if the template value in the store is 2 then load all the template2/profile component, if it is three load the template3/profile component.
import VueRouter from 'vue-router'
import store from '../store';
Vue.use(VueRouter)
const setComponent = componentName => {
return () => import(`../template${store.getters['detailsStore/getTemplate']}/views/${componentName}`)
}
const routes = [
{
path: '/',
redirect: '/details'
},
{
path: '/profile',
name: 'Profile',
//component: () => import('../template2/views/Profile') // - this is how i was importing the component but couldn't access vuex store
component: () => setComponent('Profile'),
},
{
path: '/details',
name: 'Details',
component: () => setComponent('Details'),
}
];
const router = new VueRouter({
mode: 'history',
routes
})
export default router
I thought creating the setComponent function would help,. but i just get a blank page. Any help would be much appreciated
Vue2:
Your component is not done loading. This will do it:
const asyncComp = async (componentName) => {
const component = await import(`../template${store.getters['detailsStore/getTemplate']}/views/${componentName}`)
return component;
};
{
path: '/profile',
name: 'Profile',
component: () => asyncComp('Profile'),
}
Vue 3 solution:
Here you will have defineAsyncComponent() available
https://vuejs.org/api/general.html#defineasynccomponent
const asyncComp = (compName) => defineAsyncComponent(() => import(`../template${store.getters['detailsStore/getTemplate']}/views/${compName}`);

Vue 3 dynamic components at router level

Dynamic imports is needed for me, eg. i have 10 layouts, but user only visited 3 layouts, I should not import all of the layouts, since its consumed unnecessary resources.
Since its dynamic import, each time i switch between Login & Register path <RouterLink :to"{name: 'Login'}" /> & <RouterLink :to"{name: 'Register'}" />, I got rerender or dynamic import the layout again.
My question is what is the better way to handle it, without rerender or dynamic import the layout again? Or can I save the dynamic import component into the current vue 3 context?
App.vue this is my app with watching the router and switch the layout based on route.meta.layout
<template>
<component :is="layout.component" />
</template>
<script>
import DefaultLayout from "./layout/default.vue";
import {
ref,
shallowRef,
reactive,
shallowReactive,
watch,
defineAsyncComponent,
} from "vue";
import { useRoute } from "vue-router";
export default {
name: "App",
setup(props, context) {
const layout = shallowRef(DefaultLayout);
const route = useRoute();
watch(
() => route.meta,
async (meta) => {
if (meta.layout) {
layout = defineAsyncComponent(() =>
import(`./layout/${meta.layout}.vue`)
);
} else {
layout = DefaultLayout;
}
},
{ immediate: true }
);
return { layout };
},
};
</script>
router/index.js this is my router with layout meta
import { createRouter, createWebHistory } from "vue-router";
import Home from "#/views/Home.vue";
import NotFound from "#/views/NotFound.vue";
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/login",
name: "Login",
meta: {
layout: "empty",
},
component: function () {
return import(/* webpackChunkName: "login" */ "../views/Login.vue");
},
},
{
path: "/register",
name: "Register",
meta: {
layout: "empty",
},
component: function () {
return import(/* webpackChunkName: "register" */ "../views/Register.vue");
},
},
{ path: "/:pathMatch(.*)", component: NotFound },
];
const router = createRouter({
history: createWebHistory(import.meta.env.VITE_GITLAB_BASE_PATH),
routes,
scrollBehavior(to, from, savedPosition) {
// always scroll to top
return { top: 0 };
},
});
export default router;
You could use AsyncComponent inside the components option and just use a computed property that returns the current layout, this will load only the current layout without the other ones :
components: {
layout1: defineAsyncComponent(() => import('./components/Layout1.vue')),
layout2: defineAsyncComponent(() => import('./components/Layout2.vue')),
},
Had this issue and Thorsten Lünborg of the Vue core team helped me out.
add the v-if condition and that should resolve it.
<component v-if="layout.name === $route.meta.layout" :is="layout">

How to use createAsyncComponent in VueRouter#4.0.0 (Vue3)?

When I fill the component field in createRouter() like this:
{
path: '/article/post',
name: 'article-post',
component: defineAsyncComponent({
loader: () => import('#/views/article-post/index.vue'),
loadingComponent: () => import('#/views/article-post/skeleton.vue'),
}),
},
Seems like it's not working.
What I want it to show a loading page when the actual page is loading.
How do I do that?
The loadingComponent value must be a component definition, and cannot itself be an async component:
import { createRouter } from 'vue-router'
import { defineAsyncComponent } from 'vue'
import Skeleton from '#/views/article-post/skeleton.vue'
export default createRouter({
routes: [
{
path: '/article/post',
name: 'article-post',
component: defineAsyncComponent({
loader: () => import('#/views/article-post/index.vue'),
//loadingComponent: () => import('#/views/article-post/skeleton.vue'), ❌ cannot be dynamic import
loadingComponent: Skeleton ✅
}),
},
]
})
Also note that the loading component (Skeleton) is only shown once, i.e., if the component definition is not already in cache. This Codesandbox adds an artifical delay in loader() to demonstrate.
You can load components asynchronously like this:
{
path: '/article/post',
name: 'article-post',
component: () => ({
component: import('#/views/article-post/index.vue')
loading: import('#/views/article-post/skeleton.vue')
})
},
Note
Do not use Async components for routes. Async components can still be used inside route components but route component themselves are just dynamic imports.

How can I pass data from a component to another component on vue?

I have 2 components
My first component like this :
<template>
...
<b-form-input type="text" class="rounded-0" v-model="keyword"></b-form-input>
<b-btn variant="warning" #click="search"><i class="fa fa-search text-white mr-1"></i>Search</b-btn>
...
</template>
<script>
export default {
data () {
return {
keyword: ''
}
},
methods: {
search() {
this.$root.$emit('keywordEvent', this.keyword)
location.href = '/#/products/products'
}
}
}
</script>
My second component like this :
<template>
...
</template>
<script>
export default {
data () {
return{
keyword: ''
}
},
mounted: function () {
this.$root.$on('keywordEvent', (keyword) => {
this.keyword = keyword
})
this.getItems()
},
methods: {
getItems() {
console.log(this.keyword)
....
}
}
}
</script>
I using emit to pass value between components
I want to pass value of keyword to second component
/#/products/products is second component
I try console.log(this.keyword) in the second component. But there is no result
How can I solve this problem?
Update :
I have index.js which contains vue router like this :
import Vue from 'vue'
import Router from 'vue-router'
...
const Products = () => import('#/views/products/Products')
Vue.use(Router)
export default new Router({
mode: 'hash', // https://router.vuejs.org/api/#mode
linkActiveClass: 'open active',
scrollBehavior: () => ({ y: 0 }),
routes: [
{
path: '/',
redirect: '/pages/login',
name: 'Home',
component: DefaultContainer,
children: [
{
path: 'products',
redirect: '/products/sparepart',
name: 'Products',
component: {
render (c) { return c('router-view') }
},
children : [
...
{
path: 'products',
name: 'products',
component: Products,
props:true
}
]
},
]
},
{
path: '/products/products',
name: 'ProductsProducts', // just guessing
component: {
render (c) { return c('router-view') }
},
props: (route) => ({keyword: route.query.keyword}) // set keyword query param to prop
}
]
})
From this code...
location.href = '/#/products/products'
I'm assuming /#/products/products maps to your "second" component via vue-router, I would define the keyword as a query parameter for the route. For example
{
path: 'products',
name: 'products',
component: Products,
props: (route) => ({keyword: route.query.keyword}) // set keyword query param to prop
}
Then, in your component, define keyword as a string prop (and remove it from data)
props: {
keyword: String
}
and instead of directly setting location.href, use
this.$router.push({name: 'products', query: { keyword: this.keyword }})
There are some ways to do it in Vue.
Use EventBus with $emit like you did;
event-bus.js
import Vue from 'vue';
const EventBus = new Vue();
export default EventBus;
component1.vue :
import EventBus from './event-bus';
...
methods: {
my() {
this.someData++;
EventBus.$emit('invoked-event', this.someData);
}
}
component2.vue
import EventBus from './event-bus';
...
data(){return {changedValue:""}},
...
mounted(){
EventBus.$on('invoked-event', payLoad => {
this.changedValue = payload
});
}
Use Vuex store, will be accessible at any component, at any page; (my favorite way)
store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = () =>
new Vuex.Store({
store: {myStore:{value:0}},
actions:{["actionName"]:function({commit}, data){commit("actionName", data);}}, // I usualy using special constant for actions/mutations names, so I can use that here in code, and also in components
mutations:{["actionName"]:function(state, data){state.myStore.value = data;}},
getters:{myStoreValue: state => !!state.myStore.value}
})
component1.vue
...
methods:{
change:function(){
this.$store.dispatch("actionName", this.someData); //nuxt syntax, for simple vue you have to import store from "./../store" also
}
}
component2.vue
...
data(){return {changedValue:""}},
...
mounted(){
this.changedValue = this.$store.getters.myStoreValue;
}
Use props like #Phil said.

vue-router neither watch route or navigation guards firing

Using vue-router in a single page application with the code below, the watch $route function in not firing when redirecting to mycomponent.
Also the beforeRouteUpdate in mycomponent is also not firing.
How can I detect when a variable has been tagged on to a route during component load?
App.vue
<template>
<router-view></router-view>
</template>
<script>
import Vue from 'vue'
export default {
name: 'app'
}
</script>
index.js
import Vue from 'vue'
import Router from 'vue-router'
import MyView from '#/views/MyView'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
redirect: '/home',
name: 'Home',
children: [
{
path: '/mycomponent',
name: 'MyComponent',
component: MyComponentView
},
{
path: '/mycomponent/:id',
component: MyComponentView,
props: true
}
]}]})
mycomponent.vue
<template>
<component :is="activeComponent" :id="id"></component>
</template>
<script>
export default {
name: 'MyComponentView',
components: {
...
},
mounted: function() {
#this logs path in browser
console.log('>>mounted route: ' + this.$route.path)
},
watch: {
'$route': function () {
#this does not fire
console.log('route watcher: ' + this.$route.path)
}
},
beforeRouteUpdate (to, from, next) {
#this does not fire
console.log('>>beforeRouteUpdate')
},
data () {
return {
activeComponent: 'somecomponent'
}
}
}
</script>
component1.vue
...
mounted: function() {
Event.$on('vue-tables.row-click', function(data) {
#this logs correct information in browser
console.log('data.row.id: ' + data.row.id)
router.push({path: 'mycomponent', query: {id: data.row.id}})
})
},
...
It doesn't work because beforeRouteUpdate is in component which is going to reload (Look at Life cycle of Vue). When you change the route, watch & beforeRouteUpdate is terminated and you won't see any results. In this scenario you should provide something like this:
MainRouterView.vue
<template>
<router-view/>
</template>
<script>
name: 'MainRouterView',
beforeRouteUpdate (to, from, next) {
console.log('beforeRouteUpdate')
},
</script>
router.js
export default new Router({
routes: [
{
{
path: '/mycomponent',
name: 'MainRouterView',
component: MainRouterView,
children: [
{
path: '/mycomponent/:id',
component: SecondComponent,
}
]
},
}]})
But if you want to stick up with your structure and check the status of the current route, you can replace beforeRouteUpdate to beforeRouteEnter or beforeRouteLeave in the component. You can use global guard beforeEach in router as well.
To better understand how beforeRouteUpdate works, check out this snippet: http://jsfiddle.net/yraqs4cb/