Building a Glimmer Router with page.js
Tags: glimmer, routing, glimmerjs
Published: 2020-12-29 12:50:19

This is the second post in the series of building a Router component in Glimmer.js. In the [first post](https://dev.to/rajasegar/building-a-router-component-for-glimmer-js-52bk) we built our Router component from scratch for handling anchor tag clicks, history state changes and so on. In this post we are going to make use of a client-side routing library called `page.js` for all those boilerplate code.

{% post https://dev.to/rajasegar/building-a-router-component-for-glimmer-js-52bk %}

## About page.js
[page](https://github.com/visionmedia/page.js) is a tiny client-side router inspired by the [Express](https://expressjs.com/) router. It has got a lot of features like 404 support, plugin integrations, using history state to cache data and more.

## App.js
This is how our template markup is going to look like for the main `App` component in our project. As you can see, we have omitted all the custom componets like `<Link/>` and `<Route/>`. Because all of them seems to be irrelevant here since we are going to use the  `page` library, it can make use of the standard anchor tags in the browser and the route mapping will be done in pure javascript with the `page()` functions.

```html
<nav>
<ul>
  <li><a href="/">Home</a></li>
  <li><a href="/about">About</a></li>
  <li><a href="/contact">Contact</a></li>
</ul>
</nav>
<Router/>
```

## Router Component
Our Router component will become slim here. Because we are removing the `Link` and the `Route` components since their functionality is taken care by page.js. Since page.js is going to take care of handling all the anchor click events we don't need a custom component to render our anchor tags. And the router registry which we created in our first post is also not required becuase page.js has its own way of keeping the route mappings.

Here we are using a modifier called `routes` which is a function from a file called `routes.js` where we will delegate all the routing logic to the page.js.

```js
import routes from './routes.js';

import {
  createTemplate,
  setComponentTemplate,
  templateOnlyComponent,
} from '@glimmer/core';

const Router = setComponentTemplate(createTemplate({ routes },`
      <div id="glimmer-router-outlet" {{ routes }}></div>
   `), templateOnlyComponent())

export { Router };
```

Let's take a look at our `routes.js` file. First we are importing `renderComponent` function from `@glimmer/core` library. Then we are importing the `page` library here with the default import. The main function takes an `element` parameter, which is nothing but a reference to the actual DOM element sent by our Glimmer engine when the element is inserted in to the DOM. This acts as our mount point for rendering the various page components into our DOM when a particular route is visited.

## routes.js

```js
import { renderComponent } from '@glimmer/core';
import page from 'page';

export default function(element) {
}
```

This is how we can make use of `page` library to implement our routing. These are some example patterns from their project readme page.

```js
page('/', index)
page('/user/:user', show)
page('/user/:user/edit', edit)
page('/user/:user/album', album)
page('/user/:user/album/sort', sort)
page('*', notfound)
page()
```

As you can see the `page()` function take two parameters, one the URL pattern with all the parent and child routes, and the dynamic segment placeholders. The second parameter is a callback function which handles the routing logic when the particular URL is visited.

Next we are going to use the `page` function to render our components based on the route. Let's focus on the home page route which takes an URL like `/`. For this route we are going to render a component called `Home` from a folder called `pages`. This is just for convention, to keep things organized like all the page level components are created in the `pages` folder.

```js
...
export default function(element) {
  page('/', () => {
    import('./pages/Home.js').then(component => {
      element.innerHTML = '';
      renderComponent(component.default, element);
    });
  }
}
```

## Injecting services
We can also inject services into our components while rendering them. The `renderComponent` function takes an options object as the second parameter in which you can inject services and set the arguments for the component. Let's inject a service called `LocaleService` and initialize it with the language code `en_US` into our `Home` page component.

```js
...
export default function(element) {

  page('/', showPageLoad, () => {
    import('./pages/Home.js').then(component => {
      element.innerHTML = '';
      renderComponent(component.default, {
        element: element,
        owner: {
          services: {
            locale: new LocaleService('en_US'),
          },
        }
      });
    });
  });
}
```

## Page loading indicators
It might take some time to fetch our component code and render them on to the DOM. So we want to show some loading status to our users while the components are being fetched. 
With the current setup how we are going to achieve that. The  `page` library takes any number of callbacks for route handling and it will execute them one by one in the order
we are specifying. So we will add one more callback function called `pageLoad` before our anonymous rendering callback in the routing logic.

```js
function showPageLoad(ctx, next) {
  element.innerHTML = '<h1>Loading page...</h1>';
  next();
}
```

In this `showPageLoad` callback we are doing two things. First, we will set our loading indicator html markup in our mount node in the DOM called `element`. Second we 
are using the the `next` callback function given by page.js to move to the next callback in our routing logic sequence. This will allow us to do different things 
even before mounting our page level components on to the DOM.

```js
page('/', showPageLoad, () => {
  import('./pages/Home.js').then(component => {
    element.innerHTML = '';
    renderComponent(component.default, {
      element: element,
      owner: {
        services: {
          locale: new LocaleService('en_US'),
        },
      }
    });
  });
});
```

Finally we have to call the `page()` method to start routing and listening for anchor clicks and navigation events like history state changes in the browser.

```js

export default function(element) {
...
  page();
}
```


This is how our final `routes.js` file is going to look like for handling various routes like `/about`, `/contact`, etc., You can see we are using the `showPageLoad` callback prior to rendering the respective page components.

```js
import { renderComponent } from '@glimmer/core';
import page from 'page';

export default function(element) {

  function showPageLoad(ctx, next) {
    element.innerHTML = 'Loading page...';
    next();
  }

  page('/', showPageLoad, () => {
    import('./pages/Home.js').then(component => {
    element.innerHTML = '';
    renderComponent(component.default, {
      element: element,
      owner: {
        services: {
          locale: new LocaleService('en_US'),
        },
      }
    });
    });
  });
  page('/about',showPageLoad, () => {
    import('./pages/About.js').then(component => {
    element.innerHTML = '';
    renderComponent(component.default, element);
    });
  });
  page('/contact', showPageLoad, () => {
    import('./pages/Contact.js').then(component => {
    element.innerHTML = '';
    renderComponent(component.default, element);
    });
  });

  page();
  
}
```

## Source code
You can see the full source code for this post in the [glimmer-snowpack](https://github.com/rajasegar/glimmer-snowpack) repository, which is actually a [create-snowpack-app](https://github.com/snowpackjs/snowpack/tree/master/create-snowpack-app/cli) template to build Glimmer apps with [Snowpack](https://snowpack.dev). You can use this template like below:

```
npx create-snowpack-app my-glimmer-app --template glimmer-snowpack
```

Please let me know any feedback or queries in the comments section. In the next post in this series, we are going to make use of another client side routing library to build our `Router` component.