Skip to content

Aurelia Templating — Advanced Template Features

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Aurelia Templating. We cover key concepts, practical examples, and best practices to help you master this topic.

Aurelia templating extends HTML with powerful features: template composition for reusing template fragments, template controllers like if and repeat, part replacement for customization, and advanced binding expressions for complex UI logic.

What You'll Learn

You will learn template composition with compose, template controllers (if, repeat, switch), the replaceable part system, and advanced binding expressions.

Why It Matters

Advanced templating eliminates code duplication. Reusable template fragments, conditional rendering, and dynamic composition keep templates DRY and maintainable.

Real-World Use

A product listing page uses compose to render different card layouts, repeat for the product grid, if for empty states, and replaceable parts for customizing individual card sections.

flowchart LR
    A[Template] --> B[compose]
    A --> C[repeat]
    A --> D[if/else]
    A --> E[replaceable]
    B --> F[Dynamic component]
    C --> G[Collection iteration]
    D --> H[Conditional content]
    E --> I[Customizable parts]

Template Composition with compose

The compose element dynamically renders components based on configuration.

<template>
  <!-- Compose by module ID -->
  <compose view-model="./shared/header"></compose>

  <!-- Compose with model data -->
  <compose view-model="./shared/user-card"
           model.bind="selectedUser">
  </compose>

  <!-- Compose from an array of configurations -->
  <div repeat.for="widget of dashboardWidgets">
    <compose view-model.bind="widget.component"
             model.bind="widget.data">
    </compose>
  </div>
</template>

Conditional Rendering with if and else

<template>
  <!-- Basic if -->
  <div if.bind="isAuthenticated">
    Welcome, ${user.name}!
  </div>

  <!-- if/else (Aurelia 2) -->
  <div if.bind="isLoading">
    <spinner></spinner>
  </div>
  <div else>
    <content></content>
  </div>

  <!-- Nested conditions -->
  <span if.bind="status === 'active'" class="text-green">Active</span>
  <span if.bind="status === 'pending'" class="text-yellow">Pending</span>
  <span if.bind="status === 'blocked'" class="text-red">Blocked</span>
</template>

The show Binding

Unlike if which adds/removes from DOM, show toggles visibility.

<template>
  <!-- show: toggles display:none -->
  <div show.bind="isVisible">Always in DOM, hidden when false</div>

  <!-- if: adds/removes from DOM -->
  <div if.bind="isLoaded">Added/removed from DOM</div>

  <!-- Use show for frequently toggled content -->
  <div show.bind="isDropdownOpen" class="dropdown">
    Dropdown content
  </div>
</template>

The repeat Template Controller

<template>
  <!-- Basic repeat -->
  <ul>
    <li repeat.for="item of items">${item.name}</li>
  </ul>

  <!-- Repeat with index -->
  <div repeat.for="item of items; index = $index">
    ${$index + 1}. ${item.name}
  </div>

  <!-- Repeat with first/last/even/odd -->
  <tr repeat.for="item of items"
      class="${$first ? 'first' : ''} ${$even ? 'even' : ''}">
    <td>${item.name}</td>
  </tr>

  <!-- Nested repeats -->
  <div repeat.for="category of categories">
    <h2>${category.name}</h2>
    <ul>
      <li repeat.for="product of category.products">
        ${product.name}
      </li>
    </ul>
  </div>
</template>

Switch/Case Pattern

<template>
  <div if.bind="viewMode === 'grid'">
    <grid-view items.bind="products"></grid-view>
  </div>
  <div if.bind="viewMode === 'list'">
    <list-view items.bind="products"></list-view>
  </div>
  <div if.bind="viewMode === 'table'">
    <table-view items.bind="products"></table-view>
  </div>
  <div if.bind="!viewMode">
    <p>Select a view mode</p>
  </div>
</template>

Replaceable Parts

The replaceable part system lets users customize sections of a component template.

// src/resources/elements/data-table.ts
export class DataTable {
  // Component accepts replaceable parts
}
<!-- src/resources/elements/data-table.html -->
<template>
  <table>
    <thead>
      <tr>
        <th repeat.for="col of columns">${col.label}</th>
      </tr>
    </thead>
    <tbody>
      <tr repeat.for="row of rows">
        <td repeat.for="col of columns">
          <template part="cell" part-name.bind="col.key">
            ${row[col.key]}
          </template>
        </td>
      </tr>
    </tbody>
  </table>

  <div class="pagination">
    <template part="pagination">
      <button click.delegate="prevPage()">Previous</button>
      <span>Page ${currentPage}</span>
      <button click.delegate="nextPage()">Next</button>
    </template>
  </div>
</template>

Customizing the pagination part:

<data-table columns.bind="cols" rows.bind="data">
  <template replaceable-part="pagination">
    <nav class="custom-pagination">
      <a click.delegate="goToPage(1)">First</a>
      <a click.delegate="prevPage()">Prev</a>
      <span>${currentPage} / ${totalPages}</span>
      <a click.delegate="nextPage()">Next</a>
      <a click.delegate="goToPage(totalPages)">Last</a>
    </nav>
  </template>
</data-table>

View Slots

Use <slot> for content projection (Aurelia 2).

<!-- my-panel.html -->
<template>
  <div class="panel">
    <div class="panel-header">
      <slot name="header">Default Header</slot>
    </div>
    <div class="panel-body">
      <slot></slot>
    </div>
    <div class="panel-footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>
<my-panel>
  <span slot="header">Custom Header</span>
  <p>Main content goes here</p>
  <button slot="footer">OK</button>
</my-panel>

Binding Behaviors

Binding behaviors modify how bindings work.

<template>
  <!-- Debounce an input binding -->
  <input value.bind="searchQuery & debounce:300" />

  <!-- Update on blur only -->
  <input value.bind="user.name & updateTrigger:'blur'" />

  <!-- One-time binding (never updates) -->
  <span>${config.appName & oneTime}</span>

  <!-- Signal-based binding -->
  <div>${message & signal:'my-signal'}</div>
</template>

Sanitizing HTML

Use sanitize binding behavior for safe HTML rendering.

<template>
  <!-- Safe HTML rendering (needs sanitize-html) -->
  <div innerhtml.bind="content & sanitize"></div>
</template>

Async Binding

<template>
  <!-- Directly bind to async data -->
  <div>${fetchUser().name}</div>

  <!-- Promise resolution -->
  <div>${userPromise.then(u => u.name)}</div>
</template>

Common Mistakes

  1. Overusing if instead of show. Frequently toggled content should use show to avoid repeated DOM creation/destruction.
  2. Not using repeat.for correctly. The syntax is repeat.for="item of collection". Forgetting .for or using incorrect keyword breaks the template.
  3. Mutating arrays in place when using repeat. Aurelia tracks array changes through push/pop/splice. Direct index assignment may not trigger updates.
  4. Forgetting $index, $first, $last in nested repeats. These context variables refer to the innermost repeat. Access outer repeat context with variable names.
  5. Nesting if and repeat incorrectly. Template controllers cannot be nested directly. Use <template> elements as wrappers.

Practice Questions

  1. What is the difference between if and show?
  2. How do you access the current index in a repeat loop?
  3. What is template composition used for?
  4. How do replaceable parts work?
  5. Challenge: Create a reusable CardList component that uses compose to render different card types. The component accepts an array of items where each item specifies its component type and data. Use replaceable parts to customize the card header. Demonstrate with at least three different card types.

FAQ

What are template controllers?

Built-in directives like if, repeat, switch that control template rendering logic.

Can I create custom template controllers?

Yes. Create a class with the $compile method and register it as a template controller.

What is the `replaceable` part system?

A mechanism that lets users override specific template sections when using a component.

How do binding behaviors work?

They modify binding behavior. & debounce delays updates, & oneTime prevents updates, & signal enables manual triggering.

Can I use Aurelia templating with other frameworks?

Aurelia templates are framework-specific. They require the Aurelia runtime.

Mini Project

Build a dashboard page that uses all advanced templating features: (1) compose to render dynamic widgets, (2) repeat for widget grids, (3) if/show for loading states, (4) replaceable parts for widget header/footer customization, (5) binding behaviors for debounced search and one-time config values.

What's Next

Now that you understand templating, learn Aurelia Value Converters for data formatting. Then explore Aurelia Binding Commands for advanced binding.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro