Aurelia and Web Components — HTML Templates and Custom Elements

In this series we explore the Web Component APIs: HTML Imports, Custom elements, HTML Templates, and the Shadow DOM, seeing how each of these APIs can be used within the context of your Aurelia applications. In this installment, we’ll look at the features Aurelia provides around HTML templates and custom elements.

Regardless of which SPA frameworks you use, one of the fundamental requirements involves taking a snippet of HTML (often called a template), injecting variable values, and then rendering it to the DOM. The method that I used to use for this back in the BackboneJS days was to create a JavaScript template:

This template would then be rendered by implementing a Backbone view, which looked the template up by ID using jQuery, and rendered it using the Underscore.js templating engine. This method (termed Overloading script) was pioneered by John Resig in his Micro Templating Utility back in 2008. This approach is actually great for the most part. Because as the script tag is set to display:none by default, nothing is rendered when the template is loaded. It doesn’t actually get rendered until we choose to do this with JavaScript – the browser doesn’t parse this script as content. Further, the template is inert – the browser doesn’t attempt to process the text as JavaScript because the type is set to something other than text/javascript. However, this approach does come with a downside. If you take user input and then use then directly set the .innerHTML using the rendered template combined with this input it can lead to XSS vulnerabilities. So long as you’re aware of these vulnerabilities it’s not a problem, but ideally, there would be a standard approach that circumvents it all together. You can find out more about the pros and cons of this template method on the HTML 5 Rocks – Template Tutorial.

HTML templates provide a standard way of creating snippets of markup that are not rendered when the page is loaded, but are instead loaded at run time with JavaScript. These work in much the same way as the Overloaded JavaScript approach but do not have the drawback of increasing the likelihood of developers introducing XSS vulnerabilities into your application. In the below example HTML page I show an <info-card> HTML snippet. An important thing to note here is that image in the snippet isn’t rendered when the page is loaded. Instead, we need to explicitly create an element for it and load it into the DOM. Using templates is as simple as declaring a new <template> element with some content:

After declaring the template element, we need to query it from the DOM document.querySelector('#info-card'), clone the node, creating a new copy of the DOM fragment from the template so that it can be included in the current document document.importNode(infoCard.content, true), and then append cloned node to the current document bodyElement.appendChild(infoCardInstance):

Using HTML Templates with Aurelia

Aurelia views are created using HTML templates. When the Aurelia framework loads it parses the template and creates a view instance which it then initializes with data-binding and so on. You can find out more about the Aurelia view initialization process in this great post by Jeremy Danyow on the Aurelia Hub. Aurelia adds templating features such as repeaters, one-way and two-way data-binding, binding behaviors, input validation and so on primarily through the use of custom HTML attributes. This keeps the view syntax as close to standard HTML as possible. The equivalent info card view template in Aurelia might look something like this:

The striking thing about the template syntax in the above example is how close it is to the vanilla web component template we saw earlier.

I was first introduced to the idea of custom elements with Angular 1 directives, which provided a way of declaring an HTML snippet (view) and linking it to a backing controller. Since then just about every SPA framework has implemented custom elements in some way shape or form. Custom elements extend the HTMLElement class and should be prefixed with an X. To refer back to the info-card template from earlier, if we wanted to implement this as a vanilla custom element we’d first need to create an XInfoCard class which derived from HTMLElement. It’s also possible to attributes on a custom element using the observedAttributes() method, and respond to changes by implementing the attributeChangedCallback in the custom attribute class:

You could then use the custom element as follows:

For various reasons (primarily performance), the Aurelia team chose to create a framework specific implementation of custom elements, rather than using the vanilla web components option. These custom elements are the bedrock of Aurelia’s component system. You create them by first implementing a view-model (for example info-card.js), which is analogous to the XInfoCard class in the vanilla web components example, and then creating the corresponding HTML view template. Instead of needing to implement custom callbacks to watch for attributes changing, Aurelia handles this by convention, as you’ll see in the below example:

info-card.js — the view-model

You then need to create the corresponding view file (which as we saw earlier is just a standard HTML template):

info-card.html — the view

You can use the custom element as follows:

<info-card message.bind="myMessage"></info-card>

This passes the message from a parent component down to the info-card component using one-way data-binding.

Whilst Aurelia doesn’t leverage vanilla custom elements under the hood, I think the trade-off is worth it. This design choice allows for the performance gains and an elegant development model.

This concludes the overview of how you can use Templates and Custom Elements in Aurelia. In the next post, we’ll the Shadow DOM, arguably the most important, and my favorite feature of web component API.

Originally published at on September 5, 2017.

Like what you read? Give Sean Hunter a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.