Slotted content
Learn how to work with slots to work with the concept of dynamic slot placeholders in your custom elements.
In Aurelia, we have two ways to project content into custom elements. In the case of Shadow DOM, we can use <slot>
and for situations where Shadow DOM is not desirable, we have <au-slot>
Slot
When working with Shadow DOM-enabled components, the <slot>
element is a web platform native way to allow content projection into components. In some instances, the <slot>
element will not be the right choice, and you will need to consider <au-slot>
(referenced below) instead.
The slot element will only work when Shadow DOM is enabled for your component. Attempting to use the slot element with it disabled will throw an error in the console.
In the case of a fictional but realistic example, we have a modal element. The user can provide content which is rendered inside of the element.
Assuming this is a Shadow DOM-enabled component, all is well. We have a custom element that allows for content to be used inside of it.
Because we named our component au-modal
we will then use it like this:
Notice how we use the attribute slot
on our content being passed in? This tells Aurelia to project our content into the default slot. Custom elements can have multiple slots, so how do we tell Aurelia where to project our content?
Named slots
A named slot is no different to a conventional slot. The only difference is the slot has a name we can reference. A slot without a name gets the name default
by default.
Now, to use our element with a named slot, you can do this:
Fallback content
A slot can display default content when nothing is explicitly projected into it. Fallback content works for default and named slot elements.
Listening to projection change
At the projection target (<slot>
element), with the slotchange
event
<slot>
element), with the slotchange
eventThe <slot>
element comes with an event based way to listen to its changes. This can be done via listening to slotchange
even on the <slot>
element, like the following example:
At the projection source (custom element host), with the @children
decorator
@children
decoratorIn case where it's not desirable to go to listen to projection change at the targets (<slot>
elements), it's also possible to listen to projection at the source with @children
decorator. Decorating a property on a custom element class with @children
decorator will setup mutation observer to notify of any changes, like the following example:
After the initial rendering, myDetails.divs
will be an array of 1 <div>
element, and any future addition of any <div>
elements to the <my-details>
element will update the divs
property on myDetails
instance, with corresponding array.
Additionally, the @children
decorator will also call a callback as a reactive change handler. The name of the callback, if omitted in the decorator, will be derived based on the property being decorated, example: divs
-> divsChanged
@children
decorator usage
@children
decorator usageNote: the @children
decorator wont update if the children of a slotted node change — only if you change (e.g. add or delete) the actual nodes themselves.
Retrieving component view models
When using @children
to target projected element components, it's often desirable to get the underlying component instances rather than the host elements of those. The @children
decorator by default automatically retrieves those instances, like the following examples:
As items
property is decorated with @children('my-item')
, its values is always a list of MyItem
instances instead of <my-item>
elements. You can alter this behavior by specifying a map option, like the following example:
In the above example, we give map
option a function to decide that we want to take the host element instead of the component instance.
Au-slot
Aurelia provides another way of content projection with au-slot
. This is similar to the native slot
when working with content projection. However, it does not use Shadow DOM. au-slot
is useful where you want externally defined styles to penetrate the component boundary, facilitating easy styling of components.
Suppose you create your own set of custom elements solely used in your application. In that case, you might want to avoid the native slots in the custom elements, as it might be difficult to style them from your application.
However, if you still want slot-like behavior, then you can use au-slot
, as that makes styling those custom elements/components easier. Instead of using shadow DOM, the resulting view is composed purely by the Aurelia compilation pipeline.
There are other aspects of au-slot
as well which will be explored in this section with examples.
An obvious question might be, "Why not simply 'turn off' shadow DOM, and use the slot
itself"? We feel that goes opposite to Aurelia's promise of keeping things as close to native behavior as possible. Moreover, using a different name like au-slot
makes it clear that the native slot is not used in this case. However, still brings slotting behavior to use.
If you have used the replaceable
and replace part
before or with Aurelia1, it is replaced with au-slot
.
Basic templating usage
Like slot
, a "projection target"/"slot" can be defined using a <au-slot>
element, and a projection to that slot can be provided using a [au-slot]
attribute. Consider the following example.
In the example above, the my-element
custom element defines two slots: one default and one named. The slots can optionally have fallback content; i.e. when no projection is provided for the slot, the fallback content will be displayed. Projecting to a slot is, therefore, also optional. However, when a projection is provided for a slot, that overrides the fallback content of that slot.
Similar to native shadow DOM and <slot/>
/[slot]
pair, [au-slot]
attribute is not mandatory if you are targeting the default slot. All content without explicit [au-slot]
is treated as targeting the default slot. Having no [au-slot]
is also equal to having explicit au-slot
on the content:
Another important point to note is that the usage of [au-slot]
attribute is supported only on the direct children elements of a custom element. This means that the following examples do not work.
Inject the projected slot information
It is possible to inject an instance of IAuSlotsInfo
in a element component view model. This provides information related to the slots inside a custom element. The information includes only the slot names for which content has been projected. Let's consider the following example.
The followingrk would be logged to the console for the instances of my-element
.
Binding scope
It is also possible to use data-binding, interpolation etc., while projecting. While doing so, the scope accessing rule can be described by the following thumb rule:
When the projection is provided, the scope of the custom element providing the projection is used.
When the projection is not provided, the scope of the inner custom element is used.
The outer custom element can still access the inner scope using the
$host
keyword while projecting.
Examples
To further explain how these rules apply, these rules are explained with the following examples.
Projection uses the outer scope by defaultthe
Let's consider the following example with interpolation.
Although the my-element
has a message
property, but as my-app
projects to s1
slot, scope of my-app
is used to evaluate the interpolation expression. Similar behavior can also be observed when binding properties of custom elements, as shown in the following example.
Fallback uses the inner scope by default
Let's consider the following example with interpolation. This is the same example as before, but this time without projection.
Note that in the absence of projection, the fallback content uses the scope of my-element
. For completeness, the following example shows that it also holds while binding values to the @bindable
s in custom elements.
Access the inner scope with $host
$host
The outer custom element can access the inner custom element's scope using the $host
keyword, as shown in the following example.
Note that using the $host.message
expression, MyApp
can access the MyElement#message
. The following example demonstrates the same behavior for binding values to custom elements.
Let's consider another example of $host
which highlights the communication between the inside and outside of a custom element that employs <au-slot>
In the example above, we replace the 'content' template of the grid, defined in my-element
, from my-app
. While doing so, we can grab the scope of the <au-slot name="content" />
and use the properties made available by the binding expose.bind="{ person, $even, $odd, $index }"
, and use those in the projection template.
Note that $host
allows us to access whatever the <au-slot/>
element exposes, and this value can be changed to enable powerful scenarios. Without the $host
it might not have been easy to provide a template for the repeater from the outside.
The last example is also interesting from another aspect. It shows that many parts of the grid can be replaced with projection while working with a grid. This includes the header of the grid (au-slot="header"
), the template column of the grid (au-slot="content"
), or even the whole grid itself (au-slot="grid"
).
The $host
keyword can only be used in the context of projection. Using it in any other context is not supported and will throw errors with high probability.
Multiple projections for a single slot
It is possible to provide multiple projections to a single slot.
This is useful for many cases. One evident example would a 'tabs' custom element.
This helps keep things closer that belong together. For example, keeping the tab-header and tab-content next to each other provides better readability and understanding of the code to the developer. On other hand, it still places the projected contents in the right slot.
Duplicate slots
Having more than one <au-slot>
with the same name is also supported. This lets us project the same content to multiple slots declaratively, as can be seen in the following example.
Note that projection for the name is provided once, but it gets duplicated in 2 slots. You can also see this example in action here.
Listening to <au-slot>
change
<au-slot>
changeSimilar like the standard <slot>
element allows the ability to listen to changes in the content projected, <au-slot>
also provides the capability to listen & react to changes.
With @slotted
decorator
@slotted
decoratorOne way to subscribe to au-slot
changes is via the @slotted
decorator, like the following example:
After rendering, the MySummaryElement
instance will have paragraphs value as an array of 2 <p>
element as seen in the app.html
.
The @slotted
decorator will invoke change handler upon initial rendering, and whenever there's a mutation after wards, while the owning custom element is still active. By default, the callback will be selected based on the name of the decorated property. For example: paragraphs
-> paragraphsChanged
, like the following example:
### Change handler callback reminders - The change handler will be called upon the initial rendering, and after every mutation afterwards while the custom element is still active {% %}
@slotted
usage
@slotted
usageThe @slotted
decorator can be used in multiple forms:
Note: the `@slotted` decorator won't be notified if the children of a slotted node change — only if you change (e.g. add or delete) the actual nodes themselves. {% %}
With slotchange
binding
slotchange
bindingThe standard <slot>
element dispatches slotchange
events for application to react to changes in the projection. This behavior is also supported with <au-slot>
. The different are with <slot>
, it's an event while for <au-slot>
, it's a callback as there's no host to dispatch an event, for <au-slot>
is a containerless
element.
The callback will be passed 2 parameters:
An example of using slotchange
behavior may look like the following:
slotchange
callback reminders
slotchange
callback remindersThe callback will not be called upon the initial rendering, it's only called when there's a mutation after the initial rendering.
The callback pass to slotchange of
<au-slot>
will be call with anundefined
this, so you should either give it a lambda expression, or a function like the example above.The nodes passed to the 2nd parameter of the
slotchange
callback will always be the latest list of nodes.the
slotchange
callback doesn't fire if the children of a slotted node change — only if you change (e.g. add or delete) the actual nodes themselves.
Last updated