Components

Components are the fundamental building blocks of Aurelia applications. A component consists of a view-model (TypeScript class) and an optional view (HTML template) that work together to create reusable UI elements.

Basic Component Structure

Every Aurelia component starts with a simple class:

export class MyComponent {
  message = 'Hello from Aurelia!';
}

And its corresponding HTML template (no <template> wrapper is needed in Aurelia 2):

<h1>${message}</h1>

The ${message} syntax creates a binding between your view-model property and the template, automatically updating the UI when the property changes.

When to Create a Component?

Before creating a component, consider these guidelines:

Create a component when:

  • ✅ You need reusable UI that appears in multiple places

  • ✅ The UI has its own behavior and state

  • ✅ You want to encapsulate complexity (a component should do one thing well)

  • ✅ The UI represents a meaningful concept in your domain (e.g., <user-card>, <product-list>)

Use a custom attribute instead when:

  • ✅ You're adding behavior to existing elements without changing structure

  • ✅ You're creating a decorator or modifier (e.g., tooltip, draggable)

  • ✅ Multiple behaviors can be combined on the same element

  • ✅ Examples: <button tooltip="Save changes">, <div draggable sortable>

Use a value converter when:

  • ✅ You're just formatting data for display

  • ✅ The transformation is pure (same input → same output)

  • ✅ Examples: ${date | dateFormat}, ${price | currency}

Custom Elements

To create reusable custom elements, use the @customElement decorator:

Using Components

After creating a component, you need to make it available for use. There are two ways to do this:

Import the component where you need it using the <import> element:

This is the recommended approach because:

  • Components are only loaded where they're used

  • Better code organization and maintainability

  • Clear dependencies in each template

Option 2: Global Registration

Register components globally in your main.ts to use them anywhere without imports:

Now <hello-world></hello-world> works in any template without <import>.

When to use global registration:

  • Components used on almost every page (headers, footers, layout components)

  • Shared UI components used throughout the app

  • Components you want available in all templates by default

When to use local imports:

  • Feature-specific components

  • Most custom components

  • Better tree-shaking and bundle optimization

Bindable Properties

Make component properties configurable from the outside using @bindable:

Use the component by binding values to its properties:

Component Lifecycle

Components have lifecycle hooks for initialization and cleanup:

Common Component Patterns

Pattern: Container/Presenter (Smart/Dumb Components)

Use case: Separate data management from presentation logic.

Container (Smart) Component - Manages data and business logic:

Presenter (Dumb) Component - Pure presentation, no data fetching:

Why this works: Container components handle complexity (data, routing, state), while presenter components are simple, reusable, and easy to test. You can reuse <user-list> anywhere without worrying about data fetching.

Pattern: Composition with Slots

Use case: Create flexible container components that accept custom content.

Usage:

Why this works: Slots make components flexible without needing dozens of bindable properties. The component controls the structure while consumers control the content.

Pattern: Form Components with Two-Way Binding

Use case: Reusable form inputs that work seamlessly with parent form state.

Usage:

Why this works: Two-way binding with BindingMode.twoWay keeps the parent and child in sync automatically. Changes in either place propagate to both.

Pattern: Stateful UI Components

Use case: Components that manage their own internal state.

Why this works: The component owns its UI state (which item is expanded) while accepting data as props. This keeps the parent simple - it just provides data, not UI state.

Pattern: Event-Emitting Components

Use case: Child components that notify parents of user actions.

Usage:

Why this works: Components can communicate via callbacks (tight coupling) or events (loose coupling) depending on your needs. Use callbacks for parent-child communication, events for unrelated components.

Best Practices

Keep Components Focused

  • ✅ Each component should have one clear responsibility

  • ✅ If a component is doing too much, split it into smaller components

  • ❌ Avoid "god components" that handle everything

Favor Composition Over Inheritance

  • ✅ Use slots and component composition

  • ✅ Build complex UIs from simple, reusable pieces

  • ❌ Avoid deep inheritance hierarchies

Make Components Predictable

  • ✅ Use bindable properties for inputs

  • ✅ Use callbacks or events for outputs

  • ✅ Document what bindables are required vs optional

  • ❌ Don't manipulate parent state directly

Test-Friendly Components

  • ✅ Presenter components are easy to test (just props)

  • ✅ Keep business logic in services, not components

  • ✅ Use dependency injection for testability

What's Next

Last updated

Was this helpful?