Search code examples
javascriptaurelia

Aurelia conditionally wrapping slots in components


I'm creating Aurelia components which wrap material-components-web, cards specifically right now and am wondering what's the correct way of implementing multiple content sections (actions, etc.).

Slots seem to be the right choice but I cannot just put the actions div on the template at all times, but only if any actions are actually present.

Simply put I need to check if a slot has been defined inside the component template.

<template>
    <div class="card">
        <div class="content">
            <slot></slot>
        </div>

        <!-- somehow check here if the slot has been defined -->
        <div class="actions">
            <slot name="actions"></slot>
        </div>
    </div>
</template>

Solution

  • Out-of-the-box, there is no direct way to do this like a $slots property, however you should be able to access slots via the template controller instance itself: au.controller.view.slots - the specific slot inside of this array has more information about the slot itself and its children.

    Here is an example of an Aurelia application with a modal component (custom modal element). The modal itself has a slot where HTML can be projected inside of it. We have a header, body and footer.

    Each predefined slot inside of our custom element should show up inside of a children object, where the property name is the name of our slot. If you do not provide a name for a slot (the default slot) the name of it internally is: __au-default-slot-key__.

    We first check if the slot exists and then we check the length of its children, the children array exists inside each slot. If a slot has no HTML projected into it, it will have a children length of zero. This is reliable, because default content defined inside of the slot does not get put into the children array, only projected HTML does.

    You'll see the work is being done mostly inside of modal.html, but pay close attention to modal.js where we inject the element reference of the custom element and then access the Aurelia instance using au to get to the controller containing our slots itself.

    There is one caveat with this approach: you cannot use if.bind to conditionally remove HTML inside of your custom element. If you use if.bind on a DIV containing a slot, it actually removes its slot reference so it can't be checked. To work around this, just use show.bind (as I do in my provided running example).