Side-by-side comparison
Welcome to the comprehensive guide for migrating from Aurelia 1 to Aurelia 2! This guide will walk you through every aspect of the migration process, highlighting what's changed, what's improved, and what you need to know to successfully upgrade your application.
Aurelia 2 represents a significant evolution of the framework, bringing modern JavaScript features, improved performance, better tooling support, and a more streamlined developer experience. While many core concepts remain familiar, there are important changes that will make your applications more maintainable and powerful.
Table of Contents
Application Bootstrapping
The HTML Entry Point
The way you initialize your Aurelia application has been simplified and modernized.
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My Aurelia App</title>
</head>
<body aurelia-app="main">
<script src="scripts/vendor-bundle.js" data-main="aurelia-bootstrapper"></script>
</body>
</html>
The aurelia-app
attribute told the framework where to find your main configuration file, and you had to include the aurelia-bootstrapper script.
Application Configuration and Startup
The main configuration file has been significantly streamlined, with a more intuitive API and better bundler compatibility.
// src/main.ts
export function configure(aurelia: Aurelia): void {
aurelia.use
.standardConfiguration()
.feature(PLATFORM.moduleName('resources/index'))
.globalResources(PLATFORM.moduleName('./shared/nav-bar'));
aurelia.use.developmentLogging(environment.debug ? 'debug' : 'warn');
if (environment.testing) {
aurelia.use.plugin(PLATFORM.moduleName('aurelia-testing'));
}
aurelia.start()
.then(() => aurelia.setRoot(PLATFORM.moduleName('app')));
}
Key concepts in v1:
PLATFORM.moduleName
: Required for Webpack compatibility - you had to wrap every module referencestandardConfiguration()
: Loaded default framework featuresglobalResources()
: Made components available app-widefeature()
: Loaded grouped functionalityplugin()
: Added third-party packagessetRoot()
: Defined the root component
Registering Global Resources
Making components available throughout your app is now more flexible and type-safe.
// Individual registration
aurelia.use.globalResources(
PLATFORM.moduleName('shared/value-converters/date-format'),
PLATFORM.moduleName('shared/components/loading-spinner')
);
// Feature-based registration
// resources/index.ts
export function configure(config: FrameworkConfiguration) {
config.globalResources([
PLATFORM.moduleName('./value-converters/date-format'),
PLATFORM.moduleName('./components/loading-spinner')
]);
}
Components and Templates
Component Structure
Components in Aurelia 2 maintain the same view-model pairing but with enhanced conventions and flexibility.
<!-- src/app.html -->
<require from="./shared/nav-bar"></require>
<require from="./styles.css"></require>
<template>
<nav-bar router.bind="router"></nav-bar>
<div class="content">
<h1>${message}</h1>
<router-view></router-view>
</div>
</template>
// src/app.ts
import { autoinject } from 'aurelia-framework';
import { Router } from 'aurelia-router';
@autoinject
export class App {
public message: string;
constructor(private router: Router) {
this.message = 'Welcome to Aurelia!';
}
}
/* src/app.css - had to be manually required */
.content {
padding: 20px;
}
Template Syntax Enhancements
Aurelia 2 maintains familiar binding syntax while adding powerful new features.
<template>
<!-- Basic binding -->
<div class="user-card ${isActive ? 'active' : ''}">
<img src.bind="user.avatar" alt="Avatar">
<span>${user.name}</span>
</div>
<!-- Conditional rendering -->
<div if.bind="showDetails">
<p>${user.bio}</p>
</div>
<!-- Repeating -->
<ul>
<li repeat.for="item of items" class="${$index % 2 === 0 ? 'even' : 'odd'}">
${item.name}
</li>
</ul>
</template>
Component Lifecycle
The component lifecycle has been expanded and refined with more hooks and better async support.
export class UserProfile {
private user: User;
// Component lifecycle hooks
constructor() {
// Basic initialization only
}
created() {
// Called after the component is created
this.initializeDefaults();
}
bind() {
// Called when binding begins
this.loadUserData();
}
attached() {
// Called when added to DOM
this.setupEventListeners();
}
unbind() {
// Called when unbinding
this.cleanup();
}
detached() {
// Called when removed from DOM
this.removeEventListeners();
}
}
V1 Lifecycle Order: constructor β created β bind β attached β detached β unbind
Dependency Injection
Aurelia 2's DI system has been significantly enhanced with better type safety and more flexible registration options.
import { autoinject, Container } from 'aurelia-framework';
// Service registration
const container = new Container();
container.registerSingleton(UserService);
container.registerTransient(ApiClient);
container.registerInstance(IConfig, { apiUrl: '/api' });
// Using services
@autoinject
export class UserManager {
constructor(
private userService: UserService,
private apiClient: ApiClient
) {}
}
// Manual injection
export class UserManager {
static inject = [UserService, ApiClient];
constructor(userService, apiClient) {
this.userService = userService;
this.apiClient = apiClient;
}
}
// Resolvers
import { Lazy, All, Optional, Parent, Factory, NewInstance } from 'aurelia-framework';
@autoinject
export class ComplexService {
constructor(
@Lazy(ExpensiveService) private expensiveServiceFactory: () => ExpensiveService,
@All(IPlugin) private plugins: IPlugin[],
@Optional(IOptionalService) private optionalService: IOptionalService
) {}
}
Logging System
The logging system has been completely redesigned with a more powerful and flexible architecture.
// main.ts
import * as LogManager from 'aurelia-logging';
import { ConsoleAppender } from 'aurelia-logging-console';
export function configure(aurelia) {
LogManager.addAppender(new ConsoleAppender());
LogManager.setLevel(LogManager.logLevel.debug);
aurelia.start().then(() => aurelia.setRoot());
}
// Custom appender
export class CustomAppender {
debug(logger, ...rest) {
console.debug(`DEBUG [${logger.id}]`, ...rest);
}
info(logger, ...rest) {
console.info(`INFO [${logger.id}]`, ...rest);
}
warn(logger, ...rest) {
console.warn(`WARN [${logger.id}]`, ...rest);
}
error(logger, ...rest) {
console.error(`ERROR [${logger.id}]`, ...rest);
}
}
// Using logger
import { getLogger } from 'aurelia-logging';
export class UserService {
private logger = getLogger('UserService');
loadUser(id: string) {
this.logger.debug(`Loading user ${id}`);
}
}
Router and Navigation
The router has been redesigned with a focus on type safety, better performance, and modern navigation patterns.
// Router configuration
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router) {
config.title = 'My App';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'pages/home', nav: true, title: 'Home' },
{ route: 'users/:id', name: 'user', moduleId: 'pages/user-detail', title: 'User' },
{ route: 'admin/*path', name: 'admin', moduleId: 'areas/admin/index', title: 'Admin' }
]);
this.router = router;
}
}
// Lifecycle hooks
export class UserDetail {
user: User;
canActivate(params: any, routeConfig: RouteConfig, navigationInstruction: NavigationInstruction) {
// Check if user can access this route
if (!this.authService.isAuthenticated()) {
return new Redirect('login');
}
return true;
}
activate(params: any, routeConfig: RouteConfig, navigationInstruction: NavigationInstruction) {
// Load data for the route
return this.userService.getUser(params.id).then(user => {
this.user = user;
});
}
canDeactivate() {
// Check if user can leave
if (this.hasUnsavedChanges()) {
return confirm('You have unsaved changes. Are you sure you want to leave?');
}
return true;
}
deactivate() {
// Cleanup when leaving
this.cleanup();
}
}
Data Binding
Data binding has been enhanced with better performance, new features, and more intuitive syntax.
<template>
<!-- Basic binding -->
<input value.bind="name" />
<input value.two-way="email" />
<span textcontent.bind="message"></span>
<!-- Event binding -->
<button click.trigger="save()">Save</button>
<button click.delegate="delete($event)">Delete</button>
<!-- Class binding -->
<div class="card ${isActive ? 'active' : ''}">Content</div>
<!-- Style binding -->
<div style="color: ${textColor}; background: ${bgColor}">Styled</div>
<!-- Conditional rendering -->
<div if.bind="showDetails">Details here</div>
<div show.bind="isVisible">Visible content</div>
<!-- Repeating -->
<ul>
<li repeat.for="item of items">${item.name}</li>
</ul>
<!-- Value converters -->
<span>${date | dateFormat:'MM/dd/yyyy'}</span>
<input value.bind="amount | number:'2.0-2'">
<!-- Binding behaviors -->
<input value.bind="query & debounce:500">
<div scroll.bind="scrollPosition & throttle:100">
</template>
// Computed properties needed @computedFrom
import { computedFrom } from 'aurelia-framework';
export class UserProfile {
firstName: string;
lastName: string;
@computedFrom('firstName', 'lastName')
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
What's New in Aurelia 2
Beyond the improvements to existing features, Aurelia 2 introduces several entirely new capabilities:
Shadow DOM Support
import { useShadowDOM } from '@aurelia/runtime-html';
@useShadowDOM({ mode: 'open' })
@customElement({
name: 'isolated-component',
template: '<div class="content"><slot></slot></div>',
style: '.content { padding: 20px; color: blue; }' // Encapsulated styles!
})
export class IsolatedComponent {
// Styles won't leak out, external styles won't leak in
}
Custom Attribute Patterns
Create your own binding syntax!
import { attributePattern } from '@aurelia/template-compiler';
// Enable Angular-like syntax: <input [disabled]="isDisabled">
@attributePattern({ pattern: '[PART]', symbols: '[]' })
export class AngularStyleBinding {
['[PART]'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, rawValue, parts[0], 'to-view');
}
}
Enhanced Custom Elements
import { customElement, processContent, viewResources } from '@aurelia/runtime-html';
@processContent((node, platform) => {
// Transform template content at compile time
const slots = node.querySelectorAll('template[slot]');
// Process slots, add default content, etc.
return true; // Continue processing
})
@customElement({
name: 'enhanced-card',
template: `
<div class="card">
<header class="card-header">
<slot name="header">Default Header</slot>
</header>
<div class="card-body">
<slot>Default Content</slot>
</div>
</div>
`,
style: `
.card { border: 1px solid #ccc; border-radius: 4px; }
.card-header { background: #f5f5f5; padding: 1rem; }
.card-body { padding: 1rem; }
`
})
export class EnhancedCard {
// Advanced component features
}
Watch Decorator for Advanced Observation
import { watch } from '@aurelia/runtime-html';
export class SearchComponent {
searchTerm: string = '';
results: SearchResult[] = [];
@watch('searchTerm')
async searchTermChanged(newValue: string, oldValue: string) {
if (newValue.length > 2) {
this.results = await this.searchService.search(newValue);
} else {
this.results = [];
}
}
}
Migration Checklist
Use this checklist to ensure you've covered all aspects of your migration:
ποΈ Project Setup
π§© Components
π Data Binding
π¦ Routing
π Dependency Injection
π Logging
π¨ Templates and Features
π§ͺ Testing
π§ Advanced Features
Conclusion
Migrating from Aurelia 1 to Aurelia 2 brings significant benefits: better performance, improved developer experience, enhanced type safety, and modern JavaScript features. While there are changes to learn, the core concepts remain familiar, and the improvements make building applications more enjoyable and maintainable.
The enhanced DI system, redesigned router, automatic observation, and new template features make Aurelia 2 a powerful platform for building modern web applications. Take your time with the migration, test thoroughly, and don't hesitate to leverage the new features that make development more productive.
Remember that Aurelia 2 maintains the same philosophy of convention over configuration and stays out of your way, while providing powerful tools when you need them. The migration effort will be rewarded with a more maintainable, performant, and developer-friendly application.
Last updated
Was this helpful?