Aurelia Components — Building Views with ViewModels and Templates
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
- Forgetting
requirefor custom elements. If a custom element is not globally registered, you mustrequireit in each template that uses it. - Using
bindinstead ofone-timefor static data. One-way binding has overhead. Useone-timebinding for static data that never changes. - Not using
.delegatefor events..delegateuses event delegation for better performance. Use.triggeronly when delegation cannot work. - Modifying bound properties in child components without two-way binding. Without
.two-way, child changes to bound properties are not propagated to the parent. - Accessing DOM elements before
attached(). DOM elements do not exist untilattached()runs. Access them inattached()or later.
Practice Questions
- What are the two parts of an Aurelia component?
- What lifecycle hook runs after the component is added to the DOM?
- How do you bind a property from parent to child?
- What is the difference between
.delegateand.trigger? - Challenge: Create a component hierarchy:
ParentComponentcontains a list ofProductCardcomponents. 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
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