Aurelia Framework Explained — Complete Developer's Guide
Aurelia is a JavaScript framework for building browser, mobile, and desktop applications that uses conventions over configuration, providing powerful data binding, dependency injection, and routing with clean, readable code.
What You’ll Learn
- How Aurelia’s conventions-based approach reduces boilerplate compared to other frameworks
- How dependency injection works and why it makes testing easier
- How to use Aurelia’s data binding system (one-way, two-way, one-time)
- How routing works with convention-based route configuration
- How to compose dynamic components and validate forms
Why Aurelia Matters
Aurelia’s philosophy is “conventions over configuration.” Think of it like a well-organized office: instead of telling someone where every file goes (configuration), you follow a consistent system where pens go in the pen drawer and files go in the filing cabinet (conventions). This means you write less code to get started. The same approach powers DodaTech’s internal tools — when applications share a consistent structure, developers can jump between projects without re-learning everything. For security tools like Durga Antivirus Pro, Aurelia’s modular architecture makes it easy to swap out components without breaking the whole system.
flowchart LR
A[JavaScript & HTML Basics] --> B[Aurelia Fundamentals]
B --> C[Components & Templates]
B --> D[Dependency Injection]
C --> E[Data Binding System]
D --> E
E --> F[Routing & Navigation]
F --> G[Full Aurelia Application]
style B fill:#4a90d9,color:#fff
Core Concepts — How Aurelia Thinks
Aurelia flips the usual framework approach on its head. Instead of writing configuration files to tell the framework how to wire things together, Aurelia follows conventions — naming and file patterns that it recognizes automatically.
A Basic Component
Every Aurelia component is a pair: a JavaScript class (the view-model) and an HTML template (the view).
// my-app.js — the view-model (logic layer)
export class MyApp {
constructor() {
// This property is automatically available in the HTML template
this.message = "Hello Aurelia!";
}
}<!-- my-app.html — the view (presentation layer) -->
<template>
<!-- ${message} displays the property from the view-model -->
<h1>${message}</h1>
<!-- value.bind creates two-way binding: typing updates the property -->
<input value.bind="message">
</template>Why two files? Separating logic from presentation is a core software principle. The view-model handles data and behavior; the template handles display. If you later change how the input looks, you touch only the HTML. If you change how data loads, you touch only the JavaScript.
How Conventions Work
Aurelia follows a naming rule: a file named my-app.js automatically pairs with my-app.html. When you use <my-app> in another template, Aurelia finds the matching class automatically. No imports, no registration — it just works.
Dependency Injection — The Smart Parts Bin
Dependency Injection (DI) solves a common problem: when one class needs another class to work, how do they get connected? Without DI, you create dependencies manually inside each class, making them hard to test and change.
import { inject } from "aurelia-framework";
import { HttpClient } from "aurelia-fetch-client";
// @inject tells Aurelia: "this class needs an HttpClient instance"
@inject(HttpClient)
export class UserService {
constructor(httpClient) {
// Aurelia creates the HttpClient and passes it here automatically
this.http = httpClient;
}
async getUsers() {
const response = await this.http.fetch("/api/users");
return response.json();
}
}Line by line: The @inject decorator declares that UserService requires HttpClient. When Aurelia creates a UserService, it looks at the constructor’s parameters, creates any that are missing, and passes them in. This means you can swap HttpClient with a mock version during testing — no changes to UserService needed.
Data Binding — Keeping Data and UI in Sync
Aurelia offers three binding modes. Choosing the right one affects performance and behavior:
<template>
<!-- One-way binding: JS → HTML only -->
<!-- The display updates when message changes, but typing in the input
does NOT change message -->
<p>${message}</p>
<input value.one-way="message">
<!-- Two-way binding: JS ↔ HTML — changes in either place sync -->
<input value.two-way="message">
<!-- One-time binding: JS → HTML once, then no updates -->
<!-- Best for static values that never change after load -->
<p>${message}</p>
<input value.one-time="message">
<!-- Shorthand: .bind detects the best mode automatically -->
<!-- For form elements, it defaults to two-way -->
<input value.bind="message">
</template>When to use each: Use one-way for display-only data (a user’s name shown in a profile). Use two-way for forms where the user edits data. Use one-time for static content that never changes after page load — this is the most performant option because Aurelia doesn’t watch for changes.
Routing — Navigation Made Simple
Aurelia’s router maps URLs to components using conventions:
import { Router } from "aurelia-router";
export class App {
configureRouter(config, router) {
config.map([
// Visiting /users loads the users.js/users.html component pair
{ route: "users", moduleId: "users", title: "Users" },
// :id is a route parameter — /users/42 passes 42 as the id
{ route: "users/:id", moduleId: "user-detail", title: "User Detail" },
// Empty route defaults to home
{ route: "", redirect: "users" }
]);
}
}Why this matters: Each route automatically loads the corresponding component. The router also handles navigation history, URL generation, and lifecycle hooks — so you can pause navigation if the user has unsaved changes.
Composition — Dynamic Components
The <compose> element lets you inject components dynamically at runtime:
<template>
<!-- Dynamic composition: view-model is determined at runtime -->
<compose view-model="${currentView}"></compose>
</template>export class DynamicPage {
constructor() {
// This could come from user input, permissions, or configuration
this.currentView = "admin-panel";
}
}Why composition: Instead of using if/else blocks to show different components, you let the data decide. This is how content management systems render different page types from a single layout.
Common Mistakes
Forgetting to pair
.jsand.htmlfiles: Aurelia conventions require both files to exist with matching names. Having only one file without the other causes a silent failure — the component simply doesn’t render.Using
.bindwhen you mean.one-wayor.two-way: The shorthand.bindchooses the mode automatically, but sometimes you want to be explicit. Using.one-timeon read-only data improves performance noticeably.Not using
@injectfor dependencies: If you manuallynewup dependencies in your constructor, you lose the ability to swap them during testing. Always use DI for services, HTTP clients, and other shared resources.Ignoring the
activatelifecycle hook for async data: Loading data in the constructor is a common mistake. Use theactivate()lifecycle method instead — it’s designed for async operations and works with the router’s navigation lifecycle.Over-nesting compose components: Deep
composechains are hard to debug and slow to render. Compose one level deep at most, and use routes for multi-level navigation.
Practice Questions
What is the difference between
.one-wayand.two-waybinding? One-way binding only updates the view when the model changes. Two-way binding also updates the model when the user interacts with the view — essential for form inputs.Why would you use dependency injection instead of creating dependencies in the constructor? DI makes testing easier — you can inject mock versions of dependencies. It also promotes loose coupling between classes.
What does the
@injectdecorator do? It tells Aurelia’s DI container which dependencies a class requires. Aurelia automatically creates and passes instances of those dependencies when the class is instantiated.How does Aurelia know which template to pair with a JavaScript class? Aurelia follows naming conventions:
my-component.jspairs withmy-component.html. Both files must exist in the same directory.When should you use the
activatelifecycle hook? For loading data when a route is navigated to. It’s async-friendly and integrated with the router’s loading indicators.
Challenge
Build a contact manager app: Create an Aurelia app with a route for listing contacts (fetched from a mock API service via DI), a route for viewing a single contact (contacts/:id), and a search input with two-way binding that filters the contact list in real time. Use @inject for the data service.
FAQ
Try It Yourself
Create a simple Aurelia component using a CDN-based HTML file:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/aurelia-script@1.5.0/dist/aurelia-esnext.js"></script>
</head>
<body aurelia-app>
<my-app></my-app>
<script>
// The view-model
class MyApp {
constructor() {
this.name = "Aurelia Developer";
this.items = ["Learn binding", "Build an app", "Deploy"];
}
addItem() {
if (this.newItem) {
this.items.push(this.newItem);
this.newItem = "";
}
}
}
</script>
<!-- The template — Aurelia pairs these by convention -->
<template>
<h2>Hello, ${name}!</h2>
<input value.bind="newItem" placeholder="Add item..." />
<button click.trigger="addItem()">Add</button>
<ul>
<li repeat.for="item of items">${item}</li>
</ul>
</template>
</body>
</html>Expected output: A page showing “Hello, Aurelia Developer!” with an input, an Add button, and a list of items that grows when you click Add.
What’s Next
| Topic | Description |
|---|---|
| Compare Aurelia with a lightweight alternative | |
| Review the template language Aurelia uses | |
| Add types to your Aurelia project |
Related terms: JavaScript, HTML, TypeScript, Angular, Node.js
What’s Next
Congratulations on completing this Aurelia tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro