Au2 migration - replace-part

I’m trying to migrate a relatively large app from Aurelia 1 to Aurelia2, and I’m very confused about how to migrate code that relied on replaceable/replace-part. The documentation seems to imply that that functionality has been replaced by <au-slot>, but that seems disingenuous.

The functionality that <au-slot> provides is the same functionality that the old <slot> element provided. replaceable/replace-part was, to my knowledge, the only thing in aurelia that allowed a parent component to specify a template that a reusable component could use to modify how items are rendered in its own repeat.for list.

That meant you could do stuff like this (simplified) select component:

my-select.html:

 <div repeat.for="option of options" click.delegate="onOptionClick(option)">
    <template with.bind="option" part="optionTemplate" replaceable></template>
 </div>

app.html

<my-select options.bind="options">
    <template replace-part="optionTemplate">
        ${option.code} - ${option.title}
    </template>
</my-select

Or a translation component with replaceable placeholders:

my-translation.ts:

import { bindable, bindingMode, computedFrom } from 'aurelia-framework';

export class MyTranslationCustomElement {
    @bindable({ defaultBindingMode: bindingMode.toView})
    public value: string = '';

    @computedFrom('value')
    public get parts() {
        return this.createParts(this.value);
    }

    // PRIVATE
    private createParts(value: string) {
        const regex = /\{\d+\}/gi;
        const parts = value.match(regex);

        if (!parts || parts.length === 0 ) {
            return [{
                type: 'text',
                value: value
            }];
        }

        const output: TranslationPart[] = [];
        let index: number = undefined;
        let lastIndex: number = undefined;

        parts.forEach(part => {
            index = value.indexOf(part, lastIndex);
            lastIndex = lastIndex || 0;
            
            if (index > lastIndex) {
                output.push({
                    type: 'text',
                    value: value.substring(lastIndex, index)
                });
            }

            output.push({
                type: 'replacement',
                value: part
            });
    
            lastIndex = index + part.length;
        });
        
        if (lastIndex < value.length) {
            output.push({
                type: 'text',
                value: value.substring(lastIndex, value.length)
            });
        }

        return output;
    }
}

interface TranslationPart {
    type: 'text'|'replacement';
    value: string;
}

my-translation.html:

<template>
    <template repeat.for="part of parts"><template if.bind="part.type === 'text'">${part.value}</template><au-slot else name.bind="part.value">${part.value}</au-slot></template>
</template>

app.html:

<my-translation template.bind="'Replace {0} and {1} with slotted content'">
    <template replace-part="placeholder">
       <b if.bind="part.value === '{0}'">THIS</b>
       <a if.bind="part.value === '{1}'" href="#">THAT</<>
    </template>
</my-translation>

How can I replicate any of this in Aurelia 2?

Hi! If I am not mistaken, your example of selection can be translated using au-slot as follows.

<!--my-select.html-->
<div repeat.for="option of options" click.delegate="onOptionClick(option)">
    <au-slot name="optionTemplate"></au-slot>
 </div>
<!-- app.html -->
<my-select options.bind="options">
    <template au-slot="optionTemplate">
        ${option.code} - ${option.title}
      <!--
           if your app also has a property named `option`,  
           or you want to explicitly grab the context of `my-select`
           then you need to use `$host.option` instead.
      -->
    </template>
</my-select>

In case, you have not yet checked the au-slot docs, it is here: https://docs.aurelia.io/components/shadow-dom-and-slots#au-slot. The docs describe various scenarios. In case, your cases are more involved, you can also refer the tests written for au-slot that covers even more cases, than documented.

Happy migration! :v:

Thanks for the reply!

I’ve been reading the documentation on slotted content multiple times, yet I somehow completely missed the part in the binding scope section specifically dealing with grids :sweat_smile:.

I did manage to get it working now, but I have to explicitly use $host, otherwise I seem to always get an undefined property.

1 Like