As you may have read in one of my previous posts on using Aurelia’s Shadow DOM slots, I’m a big fan of using content projection to eliminate boilerplate code in component libraries. Shadow DOM slots allow you to create templated components to serve as high-level blueprints into which you can project custom HTML at various extensibility points known as slots.
However, one drawback of slots is that they’re not supported inside of repeaters and other template controllers. This drawback is due to the way the standard is written, so we’re unlikely to see direct support for shadow DOM slots in repeaters anytime soon (unless or until the standard is modified to support this). Aurelia provides a workaround for this in the form of replaceable parts.
Replaceable parts allow you to define a replaceable
attribute to indicate that a template has a replaceable part and the part
attribute to indicate which part can be replaced. The general problem I’ve found with replaceable parts is that this implementation detail leaks out to the consumer of the component. Ideally I wanted a way to define templated parts which didn’t force any additional implementation details onto the component consumer. In the course of my investigation, I found a really cool example of how you can create a templated list in Aurelia by building a custom element which uses the processContent
attribute to transform the template content into a replacement part. This allows the consumer of the templated element to use it without being aware that it is using the replaceable part API under the hood, leading to a much less intrusive developer experience for component consumers. In this blog post, we’ll implement a Bootstrap 4 list-group
custom element which uses the replaceable part API to allow consumers to inject custom HTML for each list item. The custom HTML we’ll inject will be a pokemon
custom element, which renders pokemon details such as name, image, and so on.
You can see this in action at this Gist.
Creating the list-group custom element
To begin, we’ll create a custom element which uses the processContent
custom attribute to replace the custom element content, transforming it into a replaceable part. You’ll notice that we’re importing the {FEATURE}
module from the aurelia-pal
package to ensure that the <template>
element exists (it doesn’t in IE). A cleaner way to do this would be to use the DOM.createTemplateElement
introduced in the late August Aurelia release.The DOM.createTemplateElement
method creates method polyfills this element type and creates one for you, returning a new element of type HTMLElement
.
The next API feature to learn about is the processContent(processor: boolean | Function)
decorator, which takes either a boolean (which can be used to tell the templating engine not to process the content of the template because you want to manage this yourself) or a function which can be used to process the content. In this case, we’re creating a makePartReplacementFromContent
function which we’ll use to do the template transformation. This example custom element is taken pretty much verbatim from a Stack Overflow answer by Jeremy Danyow (the current owner of Aurelia’s binding engine).
list-group.js – (view-model)
Next, we need to create the corresponding HTML template for this custom element view-model. This is a basic Bootstrap 4 list-group, the secret sauce is this bit <template replaceable part="item-template">${defaultValue(item)}</template>
which defines a replaceable template to be used for each list item. The part
to be replaced is the item-template
which as you’ll remember we referenced in the view-model. We’ve also defined a defaultValue
method (passed by the consumer) which is used to specify a default value in case the consumer doesn’t include any element content ${defaultValue(item)}
. We’ll use this to render the Pokemon’s name in the default case:
list-group.html – (view)
Creating the Pokemon custom element
For the custom element itself we’ll create an HTML only custom element <pokemon>
which uses a surrogate behavior to create a model
binding on the template
element:
pokemon.html – (view)
Putting it all together
With the templated list-group
custom element and the pokemon
custom element defined we can now require
both of these into the app.html
root view. The list-group
can either be templated (by injecting custom HTML into the <list-group>
node), or non-templated (by specifying a function used to get the text representation of the item). It’s worth noting here that we could inject any HTML under the list-group
element. For example we could do something like <h2>${item.name}</h2>
to achieve a different effect.
app.html
In summary
You can use Aurelia’s content projection to create custom elements that feel just like native DOM elements and are flexible enough to use as blueprints to create more specific kinds of elements. Because of Aurelia’s extensibility, we were able to overcome what at first seemed a framework limitation by using the processContent
attribute to override the default way that the templating engine processes template content. This is only one example of how you can override Aurelia’s default behavior. Using other decorators such as customElement
, customAttribute
, inlineView
, and noView
there is much more you can do.
Leave a Reply