Skip to content

Aurelia Components — Building Views with ViewModels and Templates

DodaTech Updated 2026-06-28 5 min read

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

Aurelia components consist of a ViewModel (class) and a View (HTML template). The ViewModel holds data and behavior. The View displays data and handles user interaction. The framework binds them together automatically by naming convention.

What You'll Learn

You will learn how to create components, use lifecycle hooks, work with data binding, handle events, and structure component hierarchies.

Why It Matters

Components are the fundamental building block in Aurelia. Every piece of UI is a component. Mastering components means mastering Aurelia application development.

Real-World Use

A dashboard application has components for charts, data tables, filters, and metric cards. Each component is a pair of TypeScript class and HTML template, independently testable and reusable across pages.

flowchart LR
    A[Component] --> B[ViewModel .ts]
    A --> C[View .html]
    B --> D[Properties]
    B --> E[Lifecycle hooks]
    B --> F[Methods]
    C --> G[Data binding]
    C --> H[Event binding]
    C --> I[Template composition]

Creating a Component

Generate a component:

au generate component user-card

This creates src/resources/elements/user-card.ts and src/resources/elements/user-card.html.

// src/resources/elements/user-card.ts
export class UserCard {
  // Properties (bound from parent)
  user;

  // Internal state
  isExpanded = false;
  clickCount = 0;

  // Computed property
  get fullName() {
    return `${this.user.firstName} ${this.user.lastName}`;
  }

  get initials() {
    return this.user.firstName.charAt(0).toUpperCase() +
           this.user.lastName.charAt(0).toUpperCase();
  }

  // Methods
  toggleExpand() {
    this.isExpanded = !this.isExpanded;
    this.clickCount++;
  }
}
<!-- src/resources/elements/user-card.html -->
<template>
  <div class="user-card" click.delegate="toggleExpand()">
    <div class="avatar">
      <span class="initials">${initials}</span>
    </div>

    <div class="user-info">
      <h3>${fullName}</h3>
      <p>${user.email}</p>
      ${user.isActive ? '<span class="badge active">Active</span>' : ''}
    </div>

    <div if.bind="isExpanded" class="expanded-info">
      <p>Role: ${user.role}</p>
      <p>Joined: ${user.joinDate | dateFormat}</p>
      <p>Last login: ${user.lastLogin | dateFormat}</p>
    </div>
  </div>
</template>

Using a Component

<template>
  <require from="./resources/elements/user-card"></require>

  <div class="user-grid">
    <user-card user.bind="user" repeat.for="user of users"></user-card>
  </div>
</template>

Component Lifecycle Hooks

Aurelia components have a defined lifecycle with optional hook methods.

export class LifecycleComponent {
  // 1. Constructor — component instantiation
  constructor() {
    console.log('1. constructor');
  }

  // 2. bind — data binding is complete
  bind(bindingContext) {
    console.log('2. bind', bindingContext);
  }

  // 3. attached — element added to DOM
  attached() {
    console.log('3. attached — DOM ready');
  }

  // 4. detached — element removed from DOM
  detached() {
    console.log('4. detached — cleanup');
  }

  // 5. unbind — data binding is removed
  unbind() {
    console.log('5. unbind');
  }
}

Property Binding

Bind properties from parent to child using .bind and .two-way.

<!-- Parent template -->
<template>
  <!-- One-way binding: parent → child -->
  <user-profile user.bind="selectedUser"></user-profile>

  <!-- Two-way binding: parent ↔ child -->
  <search-input value.two-way="searchQuery"></search-input>

  <!-- String interpolation -->
  <h1>${pageTitle}</h1>

  <!-- Attribute binding -->
  <a href.bind="linkUrl">${linkText}</a>

  <!-- Boolean attribute -->
  <button disabled.bind="isDisabled">Save</button>

  <!-- Class binding -->
  <div class="btn ${isActive ? 'active' : ''}">Click</div>
</template>

Event Handling

Aurelia uses .delegate for event binding.

<template>
  <!-- Click event -->
  <button click.delegate="save()">Save</button>

  <!-- Mouse events -->
  <div mouseenter.delegate="onHover()" mouseleave.delegate="onLeave()">
    Hover me
  </div>

  <!-- Keyboard events -->
  <input keydown.delegate="onKeyDown($event)" />

  <!-- Form events -->
  <form submit.delegate="onSubmit($event)">
    <input value.bind="email" change.delegate="onEmailChange()" />
    <input value.bind="name" blur.delegate="onBlur()" />
  </form>

  <!-- Custom events -->
  <my-component custom-event.delegate="handleCustomEvent($event)">
  </my-component>
</template>
export class MyComponent {
  save() {
    console.log('Saved!');
  }

  onKeyDown(event) {
    if (event.key === 'Enter') {
      this.search();
    }
  }

  onSubmit(event) {
    event.preventDefault();
    this.processForm();
  }
}

Component Communication

Components communicate through bindings and the Event Aggregator.

// Parent passes callback
export class ParentComponent {
  handleItemSelect(item) {
    console.log('Selected:', item);
    this.selectedItem = item;
  }
}
<child-component item.bind="item" on-select.call="handleItemSelect(item)">
</child-component>
// Child calls parent callback
export class ChildComponent {
  item;
  onSelect;

  selectItem() {
    if (this.onSelect) {
      this.onSelect({ item: this.item });
    }
  }
}

Using Ref for DOM Access

<template>
  <input ref="myInput" />
  <div ref="myDiv"></div>

  <button click.delegate="focusInput()">Focus Input</button>
</template>
export class MyComponent {
  myInput;
  myDiv;

  attached() {
    // DOM elements accessible after attached
    this.myInput.focus();
    this.myDiv.style.backgroundColor = 'blue';
  }

  focusInput() {
    this.myInput.focus();
  }
}

Common Mistakes

  1. Forgetting require for custom elements. If a custom element is not globally registered, you must require it in each template that uses it.
  2. Using bind instead of one-time for static data. One-way binding has overhead. Use one-time binding for static data that never changes.
  3. Not using .delegate for events. .delegate uses event delegation for better performance. Use .trigger only when delegation cannot work.
  4. Modifying bound properties in child components without two-way binding. Without .two-way, child changes to bound properties are not propagated to the parent.
  5. Accessing DOM elements before attached(). DOM elements do not exist until attached() runs. Access them in attached() or later.

Practice Questions

  1. What are the two parts of an Aurelia component?
  2. What lifecycle hook runs after the component is added to the DOM?
  3. How do you bind a property from parent to child?
  4. What is the difference between .delegate and .trigger?
  5. Challenge: Create a component hierarchy: ParentComponent contains a list of ProductCard components. Each product card has title, price, and a buy button. When the buy button is clicked, the parent is notified via callback binding. The parent tracks the total number of items bought.

FAQ

Can a component have multiple templates?

No, each component has one template. Use composition to include partial templates.

What is the difference between `bind` and `one-time`?

bind creates a one-way binding that updates when the source changes. one-time renders once and never updates.

How do I conditionally render content?

Use if.bind or show.bind. if removes/adds DOM elements. show toggles visibility.

Can I use Aurelia components without a ViewModel?

Yes. A template-only component has no ViewModel file.

How do I pass content into a component?

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

Mini Project

Create a ProductList component that displays products from an array of product objects. Each product is rendered using a ProductCard component. The product card shows name, price, rating, and stock status. Include a search filter at the top that filters products in real-time. Use all lifecycle hooks with console logs.

What's Next

Now that you understand components, learn Aurelia Custom Elements for reusable UI. Then explore Aurelia Custom Attributes for element behavior.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro