loading...

How to display a route as a modal in Vue.js

charleswritescode profile image Charles-Eugene Loubao ・4 min read

Hello everyone!

In this tutorial we are going to learn how to display a Vue route as a modal, similar to image links on Dribble.com or Google contacts. This format is very useful when you want to show information to the user without redirecting them to another page

This tutorial assumes you are familiar with Vue.js.

First lets create a basic vue project with vue-router

vue-create $folder

For this project I also installed faker.js to create dummy data for our contact list

npm install --save faker

Then I created the following files

src/db.js (Mock database)

import faker from 'faker'

const contacts = {}

for (let index = 0; index < 10; index++) {
    const id = "user_" + index
    contacts[id] = {
        id,
        name: `${faker.name.firstName()}  ${faker.name.lastName()}`,
        jobTitle: faker.name.jobTitle(),
        email: faker.internet.email(),
        phone: faker.phone.phoneNumber(),
    }
}

export default {
    contacts
}

src/views/Contacts.vue (Contact list)

<template>
  <div class="contacts">
    <h2>Contacts</h2>
    <div class="contact-list">
      <router-link
        tag="div"
        class="item"
        :to="`/contacts/${contact.id}`"
        v-for="contact in contacts"
        :key="contact.id"
      >
        <strong>{{contact.name}}</strong>
        <br />
        <span>{{contact.jobTitle}}</span>
      </router-link>
    </div>
  </div>
</template>

<script>
import db from "@/db.js";
export default {
  computed: {
    contacts() {
      return Object.values(db.contacts);
    }
  }
};
</script>

<style lang="scss" scoped>
.contact-list {
    .item {
        &:not(:last-child) {
            margin-bottom: 8px;
        }
        padding: 16px;
        border-radius: 4px;
        border: solid 1px #e2e2e2;
    }
}
</style>

src/views/ContactInfo.vue

<template>
  <div class="contact-info">
    <router-link to="../">X</router-link>
    <h3>{{contact.name}}</h3>

    <h4>Job Title</h4>
    <span>{{contact.jobTitle}}</span>
    3
    <h4>Email</h4>
    <span>{{contact.email}}</span>

    <h4>Phone</h4>
    <span>{{contact.phone}}</span>
  </div>
</template>

<script>
import db from "@/db.js";
export default {
  props: ["contactId"],
  data() {
    return {
      contact: ""
    };
  },
  created() {
    this.contact = db.contacts[this.contactId];
  }
};
</script>

<style lang="scss" scoped>
.contact-info {
    padding: 16px;
}
</style>

I also modified the src/router.js file to add a route for /contacts that points to the Contacts.vue component. I am also redirecting the root path to /contacts

Changes to router.js

After making these changes we get this:
Result

If we click on a user the route will display a blank screen. To fix this we need to add a route for the ContactInfo component. We need to create the route as a child of the /contacts route.

src/router.js

import ContactInfo from './views/ContactInfo.vue'
...
{
      path: '/contacts',
      component: Contacts,

      // ADDED
      children: [
        {
          path: ':contactId',
          component: ContactInfo,
          props: true
        }
      ]
    },
...

Next we need to add a router-view component to Contacts.vue

...
  <div class="contacts">
  ...
  ...
    <router-view></router-view>
  </div>
</template>

Now click a Contact and you will see this at the bottom of the screen:
Result

This is not exactly what we want. We need to make the router-view show as a modal. We have to make a few changes

Go to Contacts.vue and replace <router-view></router-view> with this

...
    <div v-if="showModal" class="modal-route">
      <div class="modal-content">
        <router-view></router-view>
      </div>
    </div>
...

We are moving router-view inside a "Modal" wrapper which will only be display when the showModal property is set to true

Then add this to style section

.modal-route {
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  background: rgba($color: #000000, $alpha: 0.5);
  .modal-content {
    width: 50%;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: white;
  }

And finally in the script section, we add watcher for the $route property and create a showModal property inside our data. showModal is set to false to prevent the modal from being displayed when we first load the page.

...
 watch: {
    $route(newVal, oldVal) {
      this.showModal = newVal.meta && newVal.meta.showModal;
    }
  },
  data() {
    return {
      showModal: false
    }
  },

Every Vue component has a $route property that contains information about the current route. (More information on the $route object Here). By watching the $route property we can be notified when the current route changed.

Next open src/router.js and add a meta property to the /contacts/:contactId route

...
component: Contacts,
children: [
  {
    path: ':contactId',
    component: ContactInfo,
    props: true
    meta: {
      showModal: true
    }
  },
...

In Vue.js the meta property is used to pass additional information to the route. meta is not to be mistaken with the props property. More info on meta here

Now click on a contact and you should see the modal

The new route is now /contacts/user_0 and the contact info is displayed in the same page.

If you refresh the page you will see that the modal is not displayed even thought the route points to /contacts/:contactId The router-view modal is only displayed when showModal is set to true. The problem is that Vue does not call a watcher by default when a component is created and since showModal is set to false when the component is rendered, the modal is not visible. To fix this we need to refactor our $route watcher like this:

src/Contacts.vue

$route: {
      immediate: true,
      handler: function(newVal, oldVal) {
        this.showModal = newVal.meta && newVal.meta.showModal;
      }
}

The handler property is the function that is executed when a changes occurs to the property we are watching. immediate:true tells Vue to check the watched property $route when the component is created. More info on watched here

Now refresh the page and the modal should be displayed properly.

I hope you enjoyed this tutorial. The full source code is available on Github ! And a demo is a working demo is available at http://friendly-brain.surge.sh

Discussion

pic
Editor guide