Note - We are using es5 technique all through the tutorial to avoid using babel and complicating things. We will focus on core concept. We will show the list of improvements to be done at last.

In the previous tutorial we created a library and hooked the react and angularjs on click of button. Now we are going to remove those buttons, and add routing capability to our framework.

If the framework has routing capability and it provides callback for route entry and route exit,
then we can use those callback functions (or lifecycle methods) to initiase/stop our react apps and angularjs app (or any other framework app).

Types of routing

There are two main types of routing - hash routing and html5 routing. Hash routing is adding '#' hash to the urls. So your urls will be http://techdoma.in/#/react http://techdoma.in/#/angularjs

Note - The above technique of using hash is now deprecated. We have better techniques like html5 pushState which allows changing browser history from javascript. Many companies like twitter moved away from this technique as it make first-page load experience slower. Here is the link to blog post as to why twitter moved away from this technique.

We are using this technique to keep things simple, and focus on wiring up our router to lifecycle of react and angularjs. Once we are done with this part, we can do many improvements like moving to html5 routing.

Html5 pushstate routing means that the urls are changed in address bar of browser from the javascript code. This means no more using hashbang in browser. Here is excerpt from mozilla docs on pushState technique

Suppose http://mozilla.org/foo.html executes the following JavaScript:

    var stateObj = { foo: "bar" };
    history.pushState(stateObj, "page 2", "bar.html");

This will cause the URL bar to display http://mozilla.org/bar.html, but won't cause the browser to load bar.html or even check that bar.html exists.

Creating the simple router

Our router will be using hashbang technique to keep things simple. Before we create our router, create a separate file for our framework code and export the library.

var lifecycle = (function(){
    var routes = []
    var hashRegex = new RegExp(/#(.*)/);

    var load = function(fn){
        fn()
    }
    var unload = function(fn){
        fn()
    }

    var router = function(fn){
        ...
    }

    var init = function(){
        ...
    }

    return {
        load: load,
        unload: unload,
        router: router,
        init: init
    }
})()

export default lifecycle

Notice that we have added two more methods to it

  • Router
  • Init

init method initialises the first rendering and attaches the listener to route change. router method accepts the route function provided by client.

Route function

Route function will accept a function. On executing this function, we will get a json that will have

  • route path information - where to show the route
  • load callback - Function to be called when the route matches
  • unload callback - Function to be called when the route exists

In the load callback, we can initialise react/angularjs/vue. In the unload callback, we can call respective unmount methods.

Router function

var router = function(fn){
    var arr = fn();
    routes = routes.concat(arr);
}

routes is an array of objects. We are accepting new routes and concatenating it with routes object. On executing the callback function fn, we get array of routes.

Init function

Init function is where all the magic happens. We will attach a listener to hashChange event.

window.addEventListener('hashchange', function(location) {
...
}

The location object contains newURL, oldURL, which is helpful to detect which route is to be exited and which is new route.

window.addEventListener('hashchange', function(location) {
    var oldHash = hashRegex.exec(location.oldURL) ? hashRegex.exec(location.oldURL)[1] : null
    var newHash = hashRegex.exec(location.newURL) ? hashRegex.exec(location.newURL)[1] : null
    if(oldHash){
        for(var i=0;i<routes.length;i++){
            if(routes[i].route === oldHash){
                routes[i].unload()
                break;
            }
        }
    }

    if(newHash){
        for(var i=0;i<routes.length;i++){
            if(routes[i].route === newHash){
                routes[i].load()
                break;
            }
        }
    }

});

The urls will be the in form of http://techdoma.in/#/angularjs. We are interest in /angularjs part. So we will use regex (/#(.*)/) to get the require route. After getting the hash url of oldURL, we will loop through the routes to call the unload method of the matching url.

if(oldHash){
    for(var i=0;i<routes.length;i++){
        if(routes[i].route === oldHash){
            routes[i].unload()
            break;
        }
    }
}

Similarly, we will loop through the routes to call load method of matching object.

if(newHash){
    for(var i=0;i<routes.length;i++){
        if(routes[i].route === newHash){
            routes[i].load()
            break;
        }
    }
}

It is important to call these two loops seperately so that unload is called before the loading of new route.

hashChange event is called only on change of hash. So we need to load some url on first load. So we will detect the hash from current url using location object, and call load method of matching object from routes array.

var hash = hashRegex.exec(location.href) ? hashRegex.exec(location.href)[1] : null
if(hash){
    for(var i=0;i<routes.length;i++){
        if(routes[i].route === hash){
            routes[i].load()
            break;
        }
    }
}

List of improvements to be done

  • Change routing to pushState
  • Use es6 techniques like forEach instead of for loop (declarative programming)
  • Remove unused functions
  • Add more features to routing like passing some options to routes on load.

We will do these improvements in next tutorial (work in progress).