James Garbutt

Software engineer & open source guy.

Routing in Polymer Elements

April 18, 2016

Edit Page

Lately, I’ve been working a lot with Polymer, a fantastic library for making web components a lot easier to produce and manage.

It really is a great library, which I see more as a wrapper than a framework. It provides an excellent binding and change notification system and allows for truly reusable components to be made.

However, the one thing which always seemed out of place to me, was the use of page.js in the Polymer Starter Kit.

To remove this out-of-place dependency, carbon-route has been introduced.

carbon-route allows you to deal with your routes the same as any other logic in your application, through Polymer elements.

An example

An example is the best way to show what this does, so here we go:

<carbon-location route="{{route}}"></carbon-location>

<carbon-route
	route="{{route}}"
	pattern="/:page"
	data="{{routeData}}"
	tail="{{subRoute}}">
</carbon-route>

Take straight from the docs, this simply binds in window.location through the carbon-location element and produces a route object for us.

The route object essentially holds the state, such as the current path, query string, parameters and so on (now bound to route in the example).

The first two parameters are obvious, route is the current route state and routeData holds our parameters (:page).

The cool part here is subRoute.

Sub routing

In page.js, we could only really specify entire routes and what page (e.g. in an iron-pages) is associated with each.

With carbon-route, we now have the ability to match a route and pass the remainder down to be routed/handled further by other elements.

In index.html:

<carbon-route
	route="{{route}}"
	pattern="/:page"
	data="{{routeData}}"
	tail="{{subRoute}}">
</carbon-route>

<iron-pages
	attr-for-selected="data-route"
	selected="{{routeData.page}}">

	<my-page
		data-route="test"
		route="{{subRoute}}">
	</my-page>

</iron-pages>

Here, if we visit /test then routeData.page will be test. If we visit /test/123, it will still be test. We are matching only the first part of the path essentially, any unmatched goes in our subRoute state (as in, subRoute.path will be /123).

This means we can leave all paths underneath /test upto my-page to handle, excellent!

In my-page.html:

<carbon-route
	route="{{route}}"
	pattern="/:id"
	data="{{routeData}}">
</carbon-route>

So, as you see, my-page has no idea about /test, it only cares about paths visited below that (in this case, /123). Here, routeData.id will become 123 for /test/123.

Everything is observed

Now, the really cool thing I love about carbon-route and carbon-location is the binding.

The route object that carbon-location produces is bound into window.location, so we can deal with all location changes within Polymer:

this.set('route.path', '/foo'); // Causes a location change to /foo

Remember too, that this all uses the HTML5 history API so there are no actual changes in page.

Even better, the route data is also bound:

this.set('routeData.id', 500); // Causes a location change to /test/500

Another example

This leads to some really cool stuff, like the following:

<paper-tabs
	selected="{{routeData.page}}"
	attr-for-selected="data-route">
	
	<paper-tab data-route="home">Home</paper-tab>
	<paper-tab data-route="contact">Contact</paper-tab>
	<paper-tab data-route="about">About</paper-tab>

</paper-tabs>

This very nicely results in the location bar changing to the associated route when a tab becomes active. So if we click Home, we change location to /home because of the binding routeData.page has.

To finish

As you can see, this is such a nice addition as we can deal with routes within our Polymer app, keeping with the flow. No longer do we have to resort to external means to handle it, but can put it where it belongs, with the rest of our code.