Dynamic Routing In VueJS explained with an example
In this article, we will learn about Dynamic Routing In VueJS. Time and time again, the simplicity heralded in Vuejs has been reiterated. It does go without saying, that it is the bedrock on which the framework was built on.
“I would consider it my failure if it takes 3 years to be good at writing Vue apps” –Evan You, 2020 see here
However, making use of its libraries could be straightforward as redirecting to a Url using vue-router
and other times it isn’t always so straightforward in the whole sense of it as rerouting dynamically using the router library still.
To drive home the concept of dynamic routes in vuejs, we would build a mini-app that displays various companies in the form of a card, and on click, a specific card would redirect to a new page that contains elaborate information unique to the clicked card. To give more perspective to the scope above, let’s assume we have a data payload from our endpoint and here’s what we want:
- Loop through our payload and
- Display a list of companies in a card form ( but obviously we cant display all that we possibly need to on the card).
- We also need each card to redirect to a new page
- On the new page, we want to get data from our payload that are specifically tied to the clicked card.
To get this up and running, we will start by storing the company details in a JSON format. We would be using jsonbin to achieve this.
Before we kick off
Learn Vue.js and modern, cutting-edge front-end technologies from core-team members and industry experts with our premium tutorials and video courses on VueSchool.io.
Here is the demo of our app:
Outline
- Setup project with vue cli
- Setup file structure for our application
- Install required dependencies and libraries
- Get company data info from our endpoint
- Loop through gotten data and display on a card
- Re-route card dynamically
- Display Card-specific Info on a new page
- Conclusion
Set up the project with vue CLI
Let’s start by creating a new folder for our application in our home directory. Open your command tool and run the command:
vue create company-dir
to specify the desired package manager, run:
vue create company-dir --packageManager <package manager>
In this case, the package manager is npm for us. This would prompt us to set some project presets. We would however manually select these features and for the purpose of this, we would be using just Babel and Router.
Setup file structure for our application
Next we would need to set up our project file structure.Yes, do away with the Hello world component inside the Components folder, un-mount it as well by deleting it as an imported and registered component in the Home.vue file located in the views folder.
Go ahead and setup a file structure similar to one below:
Install required dependencies and libraries
Next, we would need to install the required libraries. To do this we would first need to enter the project directory; run the following in your command tool:
cd company-dir
We would be using Bootstrap, Axios, and vue-clamp
as packages for our development. Run the command to install the npm packages:
npm i bootstrap jquery popper.js axios vue-axios vue-clamp --save
To access the packages globally, we would have to import them into our app. To do that, we would locate our main.js file in the root of our project and edit so:main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import "bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";
import axios from "axios";
import VueAxios from "vue-axios";
const base = axios.create({
baseURL: "https://api.jsonbin.io/b/5f20829bc58dc34bf5dca275",
});
Vue.prototype.$http = base;
Vue.use(VueAxios, axios);
Vue.config.productionTip = false;
new Vue({
router,
render: (h) => h(App),
}).$mount("#app");
The first seven lines are just pretty much us ‘importing’ what we need to get the app going. I would be more particular about lines 9-12 which is someway i like configuring my HTTP client.
So what’s the big deal about this place? Basically we just created a global instance of Axios and set the base Url of our API. Now to access Axios we can make a this.$http call. Look out for this when we make our request to the server.
Get company data info from our endpoint
Next, we would need to use the get method to fetch our data, loop through and display specific information for the user. we would first create a basic card layout for the information we need to display. Go ahead and write the following in your home.vue file.
home.vue
<template>
<div>
<div class="container">
<div class="row">
<div class="col-md-6" v-for="i in 4" :key="i">
<div class="row white__bg">
<!-- Image/screenshot here -->
<div class="col-md-4 img__holder">
<div>
<img src class="img-fluid" alt />
</div>
<div class>
<h3></h3>
</div>
</div>
<!-- Company details here -->
<div class="col-md-8">
<div class="details text-left">
<h4></h4>
<h5 class="pt-3">
headquaters:
<span></span>
</h5>
<h5 class="pt-3">
Industry:
<span></span>
</h5>
<h5 class="pt-3">
Founded:
<span></span>
</h5>
<div class="inline">
<h5 class="pt-3">
Description :
<span></span>
</h5>
<router-link to="#">Read More</router-link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.white__bg {
width: 100%;
background: #f8f8f8;
border: solid #bdbdbd 0;
box-shadow: 5px 0 20px rgba(0, 0, 0, 0.1);
-webkit-box-shadow: 5px 0 20px rgba(0, 0, 0, 0.1);
-moz-box-shadow: 5px 0 20px rgba(0, 0, 0, 0.1);
padding: 2rem;
margin-top: 20px;
}
h4 {
font-size: 20px;
}
h5 {
font-size: 16px;
}
.pt-3 span {
font-size: 14px;
line-height: 22px;
}
a {
text-decoration: none !important;
}
h3 {
font-size: 22px;
margin-top: 20px;
}
@media screen and (max-width: 576px) and (min-width: 275px) {
h3 {
text-align: center;
padding-top: 30px;
}
h5 {
text-align: center;
}
h4 {
text-align: center;
}
}
@media screen and (max-width: 768px) and (min-width: 575px) {
h3 {
text-align: left;
padding-top: 30px;
}
}
</style>
This would create a layout of 4 cards for us giving us an insight on the arrangement structure of our cards sequel to we actually pulling the real data.
To have a view of what we have so far, update App.vue (present in the root of the src folder) and Home.vue files (Present in the views folder) respectively:
App.vue
<template>
<div id="app">
<router-view/>
</div>
</template>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');
#app {
font-family: 'Poppins', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
</style>
Home.vue
<template>
<div>
<home></home>
</div>
</template>
<script>
import home from "../components/Home/home";
export default {
name: "Home",
components: {
home,
},
};
</script>
Start the development server by running the command:npm run serve
Next, we would make a get request to our API, passing our secret key as a header and then, store the gotten data in an empty array that we would create in our data object. Sequel to this, we would call our function using vue’s life cycle hook, Mounted
.
To clip down the lines of texts we would be displaying, in the description part on the card to two, we would also be importing a plugin (vue-clamp) we installed in the early phases of our project.
This would prompt the script section of home.vue file to look as thus:
<script>
const axios = require('axios')
import VClamp from 'vue-clamp'
export default {
components: {
VClamp,
},
name: 'home',
data() {
return {
secretKey: '$2b$10$Wb8VppNIDoAhIAB8n8oFnuCuOnANGbL7pxknMH8lhYplG40/hi5LC',
companies: [],
}
},
mounted() {
this.getCompany()
},
methods: {
async getCompany() {
try {
let response = await this.$http.get(
`https://api.jsonbin.io/b/5f20829bc58dc34bf5dca275`,
{
headers: {
'secret-key': this.secretKey,
},
},
)
console.log(response.data)
this.companies = response.data
} catch (error) {
console.log(error.response)
}
},
},
}
</script>
If we take a close peek at our console, we should be getting a response to the request we made to the server.
Loop through gotten data and display on a card
Using the v-for directive, we would be looping through the data we have stored in our companies array. Here’s a quick throwback. companies: [],
This line right here, in our data object, created the empty array.this.companies = response.data
This on the other hand in our getCompany function, pushed our gotten response into the initially empty array.
So back to the looping and displaying. This would prompt our home.vue file to look as thus:
home.vue
<template>
<div>
<div class="container">
<div class="row">
<div class="col-md-6" v-for="company in companies" :key="company.objectID">
<div class="row white__bg">
<!-- Image/screenshot here -->
<div class="col-md-4 img__holder">
<div>
<img :src="`${company.logo}`" class="img-fluid" alt="" />
</div>
<div class>
<h3>{{ company.company_name }}</h3>
</div>
</div>
<!-- Company details here -->
<div class="col-md-8">
<div class="details text-left">
<h4>{{ company.title_ }}</h4>
<h5 class="pt-3">
headquaters:
<span> {{ company.headquarters }}</span>
</h5>
<h5 class="pt-3">
Industry:
<span> {{ company.industry }}</span>
</h5>
<h5 class="pt-3">
Founded:
<span> {{ company.founded }}</span>
</h5>
<div class="inline">
<h5 class="pt-3">
Description :
<span>
<v-clamp autoresize :max-lines="2">
{{ company.Short_description }}
</v-clamp>
</span>
</h5>
<router-link to="#">Read More</router-link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const axios = require('axios')
import VClamp from 'vue-clamp'
export default {
components: {
VClamp,
},
name: 'home',
data() {
return {
secretKey: '$2b$10$Wb8VppNIDoAhIAB8n8oFnuCuOnANGbL7pxknMH8lhYplG40/hi5LC',
companies: [],
}
},
mounted() {
this.getCompany()
},
methods: {
async getCompany() {
try {
let response = await this.$http.get(
`https://api.jsonbin.io/b/5f20829bc58dc34bf5dca275`,
{
headers: {
'secret-key': this.secretKey,
},
},
)
console.log(response.data)
this.companies = response.data
} catch (error) {
console.log(error.response)
}
},
},
}
</script>
<style scoped>
.white__bg {
width: 100%;
background: #f8f8f8;
border: solid #bdbdbd 0;
box-shadow: 5px 0 20px rgba(0, 0, 0, 0.1);
-webkit-box-shadow: 5px 0 20px rgba(0, 0, 0, 0.1);
-moz-box-shadow: 5px 0 20px rgba(0, 0, 0, 0.1);
padding: 2rem;
margin-top: 20px;
}
h4 {
font-size: 20px;
}
h5 {
font-size: 16px;
}
.pt-3 span {
font-size: 14px;
line-height: 22px;
}
a {
text-decoration: none !important;
}
h3 {
font-size: 22px;
margin-top: 20px;
}
@media screen and (max-width: 576px) and (min-width: 275px) {
h3 {
text-align: center;
padding-top: 30px;
}
h5 {
text-align: center;
}
h4 {
text-align: center;
}
}
@media screen and (max-width: 768px) and (min-width: 575px) {
h3 {
text-align: left;
padding-top: 30px;
}
}
</style>
This should give us a view of our five(5) companies displayed on the card. Notice we still haven’t touched our ‘Read more’ tab which is meant to handle the dynamic reroute.<router-link to="#">Read More</router-link>
Re-route card dynamically to display card-specific info
The final part of this guide would be us doing the reroute itself. We would however need to configure our router first.
The vue-router allots us a dynamic segment amongst other things and we would be using that to achieve our reroute. A dynamic segment in itself is denoted by a colon ‘: ‘.
We would update our index.js file (Housed within the router folder) as thus:
index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about/:companyid',
name: 'About',
props: true,
component: () =>
import ('../views/About.vue')
},
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
The router itself is routing to /about/:companyid,
this route however is not static or generic sort of. It is determined by the configuration embedded in the router-link tag. This configuration looks so:
<router-link :to="{name: 'About', params: { companyid: company.objectID },}"> Read More </router-link>
Go ahead and re-write this line in your home.vue file.
Ideally, the router library is concerned with two parameters, the route and the component housing the view. Here we have stated the component being ‘About’.
The route itself should be something similar to /dashboard
or as in our case /about/:companyid
, I mean something similar to our normal use of vue-routers.
However, companyid
is no more static in the whole sense of it, as it’s now subject to what is held as a value in company.objectID
.
Try clicking on the ‘read more’ link on various cards while paying attention to the address bar. We should be getting redirected to the same page but with a different URL. The company id in this case being the differentiator.
Finally, we need to grab Information tied to a particular card and display it on this page being routed to.
Display card-specific info on new-page
We should probably call this the peak of our journey. At this point, We have successfully looped through our data payload, displayed certain information to the user, rerouted dynamically, and now we look set to feed the user with more information regarding the card clicked.
Let’s, first of all, create components to house the information we need to display. Go ahead and update section1.vue, section2.vue and navbar.vue file (Housed within the partials folder) files as thus:
section1.vue
<template>
<div>
<div>
<div class="body">
<div class="container px-0">
<div class="row">
<!-- Details holder here -->
<div class="col-md-8">
<div class="row white__bg">
<!-- Image/screenshot here -->
<div class="col-md-6 img__holder">
<div>
<img
:src="`${companies.screenshot}`"
class="img-fluid"
alt
/>
</div>
</div>
<div class="col-md-6">
<div class="details">
<h4>
{{ companies ? companies.title_ : 'Not available' }}
</h4>
<h5 class="pt-3">
headquaters:
<span>
{{
companies ? companies.headquarters : 'Not available'
}}
</span>
</h5>
<h5 class="pt-3">
Industry:
<span>
{{ companies ? companies.industry : 'Not available' }}
</span>
</h5>
<h5 class="pt-3">
Address :
<span>
{{
companies ? companies.companyAddress : 'Not available'
}}
</span>
</h5>
<h5 class="pt-3">
Founded:
<span>
{{ companies ? companies.founded : 'Not available' }}
</span>
</h5>
<div class="inline pt-3">
<p>
Rating:
{{ companies ? companies.rating : 'Not available' }}
</p>
<p class="mx-4">
Price:
{{ companies ? companies.price : 'Not available' }}
</p>
</div>
</div>
</div>
</div>
</div>
<!-- Mini side bar here -->
<div class="col-md-4 white__bg">
<h4>Company Info</h4>
<div>
<img :src="`${companies.logo}`" alt />
</div>
<div>
<span class="badge badge-pill mt-3 badge-success">
{{ companies ? companies.type : 'Not available' }}
</span>
</div>
<div>
<h6 class="mt-3">
{{ companies ? companies.company_name : 'Not available' }}
</h6>
</div>
<div>
<h6 class="mt-3">
{{ companies ? companies.company_size : 'Not available' }}
</h6>
</div>
<div>
<a :href="'' + companies.website_" target="_blank">
{{ companies ? companies.website_ : 'Not available' }}
</a>
</div>
<div class="social__holder mt-3">
<a :href="'' + companies.facebookUrl" target="_blank">
<i class="fa fa-facebook mx-2"></i>
</a>
<a :href="'' + companies.twitterUrl" target="_blank">
<i class="fa fa-twitter mx-2"></i>
</a>
<a :href="'' + companies.linkedinUrl" target="_blank">
<i class="fa fa-linkedin mx-2"></i>
</a>
<a :href="'mailto:' + companies.email" target="_blank">
<i class="fa fa-envelope mx-2"></i>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const axios = require('axios')
export default {
name: '',
data() {
return {
secretKey: '$2b$10$Wb8VppNIDoAhIAB8n8oFnuCuOnANGbL7pxknMH8lhYplG40/hi5LC',
companies: null,
router: this.$route.params.companyid,
}
},
mounted() {
this.getCompany()
},
methods: {
async getCompany() {
try {
let response = await this.$http.get(
`https://api.jsonbin.io/b/5f20829bc58dc34bf5dca275`,
{
headers: {
'secret-key': this.secretKey,
},
},
)
// console.log(this.router)
console.log(response.data)
this.companies = response.data.find(
(company) => company.objectID === this.router,
)
console.log(this.companies)
} catch (error) {
console.log(error.response)
}
},
},
}
</script>
<style scoped>
@import url(//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css);
.body {
background: #f6f8fa;
height: 100%;
}
.white__bg {
background: white;
border: 2px solid white;
width: 100%;
box-shadow: 0 0.75rem 6rem rgba(56, 65, 74, 0.03);
margin-bottom: 24px;
border-radius: 0.25rem;
padding: 2rem;
margin-top: 20px;
}
.details {
text-align: left;
}
h3 {
font-size: 22px;
margin-top: 20px;
}
h4 {
font-size: 20px;
}
h5 {
font-size: 16px;
}
.pt-3 span {
font-size: 14px;
line-height: 22px;
}
a {
text-decoration: none !important;
}
.fa {
color: black;
}
path {
color: black;
height: 4vh;
}
path :hover {
color: blue;
}
</style>
section2.vue
<template>
<div>
<div>
<div class="body">
<div class="container px-0">
<div class="row">
<!-- More deatils card here -->
<div class="col-md-7 white__bg">
<h4>About Us</h4>
<div class="about mt-4">
<p>{{ companies ? companies.Long_description : 'Not available' }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const axios = require("axios");
export default {
name: "",
data() {
return {
secretKey: "$2b$10$Wb8VppNIDoAhIAB8n8oFnuCuOnANGbL7pxknMH8lhYplG40/hi5LC",
companies: null,
router: this.$route.params.companyid,
};
},
mounted() {
this.getCompany();
},
methods: {
async getCompany() {
try {
let response = await this.$http.get(
`https://api.jsonbin.io/b/5f20829bc58dc34bf5dca275`,
{
headers: {
"secret-key": this.secretKey,
},
}
);
this.companies = response.data.find(
(company) => company.objectID === this.router
);
} catch (error) {
console.log(error.response);
}
},
},
};
</script>
<style scoped>
.body {
background: #f6f8fa;
height: 100%;
}
.white__bg {
background: white;
border: 2px solid white;
width: 100%;
box-shadow: 0 0.75rem 6rem rgba(56, 65, 74, 0.03);
margin-bottom: 24px;
border-radius: 0.25rem;
padding: 2rem;
margin-top: 20px;
}
.about p {
text-align: left;
font-size: 14px;
line-height: 25px;
}
</style>
navbar.vue
<template>
<header>
<section class="container main-iv px-0">
<nav class="navbar navbar-expand-lg navbar-light">
<a class="navbar-brand a-text flex" href="#">
<h2>{{companies.company_name}}</h2>
</a>
</nav>
</section>
</header>
</template>
<script>
export default {
name: "",
data() {
return {
secretKey: "$2b$10$Wb8VppNIDoAhIAB8n8oFnuCuOnANGbL7pxknMH8lhYplG40/hi5LC",
companies: null,
router: this.$route.params.companyid,
};
},
mounted() {
this.getCompany();
},
methods: {
async getCompany() {
try {
let response = await this.$http.get(
`https://api.jsonbin.io/b/5f20829bc58dc34bf5dca275`,
{
headers: {
"secret-key": this.secretKey,
},
}
);
this.companies = response.data.find(
(company) => company.objectID === this.router
);
} catch (error) {
console.log(error.response);
}
},
},
};
</script>
</script>
<style scoped>
header {
width: 100%;
background: #ffffff;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.15);
margin-bottom: 40px;
}
.nav-item {
font-family: "Poppins", sans-serif;
color: #000000;
font-weight: 600;
font-size: 14px;
line-height: 19px;
font-style: normal;
}
</style>
So basically we repeated the sequence of creating the layouts and fetching our data and displaying for the user. However, at this point we are out to display card-specific information and to do that we first got a hold of our object id in our data object, here: router: this.$route.params.companyid,
Next, we used JavaScript Array find()
method, to check our list from the data payload and ‘find’ one with the id matching what we have from our router. We subsequently stored this in the companies array which we declared initially in our data object. All these happened in the script section of our file here: this.companies = response.data.find((company) => company.objectID
Finally, we would be Importing these components in our about.vue file (Housed in the About Folder) as thus:
about.vue
<template>
<div>
<cnav></cnav>
<div class="container px-0">
<div class="back">
<router-link to="/">
<button class="btn btn-outline-info mb-3">
<i class="fa fa-arrow-left mx-2"></i>
Back
</button>
</router-link>
</div>
</div>
<div>
<section1></section1>
<section2></section2>
</div>
</div>
</template>
<script>
import section1 from "./section1";
import section2 from "./section2";
import cnav from "../Partials/navbar";
export default {
name: "about",
components: {
section1,
section2,
cnav,
},
};
</script>
<style scoped>
.back {
text-align: left;
}
</style>
To see what we have so far, we would update our About.vue file (House within the views folder) as thus:
About.vue
<template>
<div>
<about></about>
</div>
</template>
<script>
import about from "../components/About/about";
export default {
name: "About",
components: {
about
},
};
</script>
This should give us our ready-made mini-app, go ahead to see that each card reroutes to a page that houses information unique to the clicked card.
Conclusion
In this guide, we have seen how to create dynamic routes in vuejs. This we drove home by building an app that displays basic company information and on click, redirects to a page with more info. We also saw how to track the information tied to this card and display it on the new page. It is noteworthy that the page being redirected to is the same. However, the information displayed is subject to the card clicked.
You can access the code source here.