How routing works in VueJS

Introduction

In one of my projects, I am working on a single page application (SPA), using vuejs to load the content into the user interface. To route to different pages, the application will make use of the hash location to identify and load the respective content and template.

The vuejs documentation features a rudimentary way of generating the content based on the path, the solution looks very promising, we will be using it as a starting point in this article.

Trying out the example

The routing example only included static templates. But that is not very useful in itself. I want to be able to route with a normal template where the interface can be data bound to update dynamically with changes in the application state.

const NotFound = { template: '<p>Page not found</p>' }
const Home = { template: '<p>home page</p>' }
const About = { template: '<p></p>' }
const routes = {
'#/': Home,
'#/about': About
}
new Vue({
el: '#app',
data: {
currentRoute: window.location.hash,
title: 'asdf'
},
computed: {
ViewComponent: function() {
return routes[this.currentRoute] || NotFound;
}
},
render: function(h){
return h(this.ViewComponent)
}
})

So I replaced the template to include some variables from the data object. Error: the template doesn’t recognize the variable. Now we have to figure out how to make vue recognize and register the variables.

Render the template

We will start off by exploring how the interface is rendered. Whenever the window.location.hash changes, the ViewComponent() changes. The function returns an object representation of a Vue Component. So first, we will have to understand how components work.

The documentation, introduced components with the prop attribute. prop is used to pass information from parent to child components. Maybe we can use it to insert information into the user interface?

nope… didn’t work

new Vue({
el: '#app',
data: {
currentRoute: window.location.hash
},
computed: {
ViewComponent: function() {
var template = routes[this.currentRoute] || NotFound;
template.prop = {
title: 'test'
};
            return template;
}
},
render: function(h){
return h(this.ViewComponent)
}
});

Information not recognized???

There is a gap in my understanding of how information is being passed into vue components, how the object makes use of the render function. To fill this gap, we have to go back to the documentation.

In the render function, the component is created with the createElement function. By inspecting the result of h(this.ViewComponent), we can have a better understanding of what the vue instance contains.

The documentation included this section which shows how the render function is being used. The render function takes in either the template attribute or the element’s outerHTML as input and compiles it into a chain of functions. (details here)

The documentation page on the render function includes some pretty interesting details about using prop too.

Rendering components using `prop`

This part of the documentation covers the use of prop to create customized components. The prop attribute provides information to describe the component. In the example, the prop attribute is used to alter the render function to generate different types of components.

But prop doesn’t look like our solution. What we are trying to do is to allow the render function to bind data to the DOM elements in the interface, not making changes the template of the components. prop doesn’t look like the thing we need.

I need to look further

I delved deeper into the results of the render function to search for clues on binding data to the component’s template.

As I explored through the different attributes, the data attribute caught my attention. I did a search on vue component’s data attribute. Turns out, this data attribute (or method, if you will) is pretty special.

Unlike a vue instance where the data attribute can be an object, for a component it must be a function.

This is to allow support for multiple components. When data is a function, it allows your component to return a different object for different instances of the component. This way, you can segregate your component’s data and different components will be bound to different data objects. A rather elegant solution.

new Vue({
el: '#app',
data: {
currentRoute: window.location.hash
},
computed: {
ViewComponent: function() {
var template = routes[this.currentRoute] || NotFound;
template.data = function(){
return {
title: 'asdf'
};
};
            return template;
}
},
render: function(h){
return h(this.ViewComponent)
}
});

So what I did is to include the data function inside the object which is passed into the render function of the vue instance. And it worked great, now the interface recognizes the data and displays the right value.

The whole VueJS model

Now that we got the code working, let’s figured out how the vue object is structured and rendered.

Each vue instance is something like a component, the whole object can be imagined to be a giant component. How a vuejs instance is rendered depends on its render function. In the default render function, vuejs checks if the component had a template attribute. If it finds one, vue will compile the template string into a string of functions (see documentation for more details).

If the template attribute doesn’t exist, vue will use $el.outerHTML instead, where $el is the DOM element your vue object is attached to. This gives developers the convenient option of including the template in the HTML body directly, so they wouldn’t need to mess with Javascript for simple data binding.

In the router example, you will be creating different vue components for each different path. What determines which interface to load is the template attribute. It is a template string that determines what will be loaded in the DOM element. It is not any different from the template string that you use normally, you can create HTML elements that are bound to different data as well.

As for the structure of the design, there is a vue instance attached to the main HTML wrapper div#app. The object is then set up such that any change in the hashpath will trigger the vue object to re-render. This is done by adding window.location.hash to the object’s data as one of the watched variables.

When the main vue object is triggered, the computed variables are reloaded, and ViewComponent be will recalculated. This will return an object that describes the component to be rendered. It will include main attributes required, such as template, and data.

Of course, the key feature enabling all of this is the render function of the vue instance. As seen in the flowchart, the render function is triggered when there is a change in any of the watched values. Our render function takes the ViewComponent object and pass it into the createElement function. This will load the corresponding interface, depending on the template that you have defined for each different route.

Of course, that is just the basic version of a vuejs router. Some additional features you might consider adding will be the support of route specific functions. This will allow you to do things such as user checks, redirecting, and in my case: load data using ajax calls.

The end

That’s all to the design of a vuejs router. The rough idea is to redraw the app view according to the current route, as determined by the hashpath. Knowing this, you will be able to extend the idea further to add support to more features. If you have any questions or know of even better routing design, do leave it in the comments below.

Show your support

Clapping shows how much you appreciated Metta Ong’s story.