From Angular to Aurelia
Angular developers: Keep the best parts (DI, TypeScript, CLI) while eliminating the complexity and improving performance.
Angular developer? You'll feel right at home with Aurelia. Keep everything you love—dependency injection, TypeScript, powerful CLI—while eliminating boilerplate, improving performance, and simplifying your development experience.
Why Angular Developers Choose Aurelia
🎯 All the Power, None of the Complexity
// Angular: Heavy ceremony and boilerplate
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil, debounceTime, distinctUntilChanged } from 'rxjs/operators';
@Component({
selector: 'app-user-search',
template: `
<div>
<input
[value]="searchQuery"
(input)="onSearchInput($event)"
placeholder="Search users..."
>
<div *ngIf="loading">Loading...</div>
<app-user-card
*ngFor="let user of filteredUsers; trackBy: trackByUserId"
[user]="user"
(userEdit)="onUserEdit($event)"
></app-user-card>
</div>
`
})
export class UserSearchComponent implements OnInit, OnDestroy {
@Input() users: User[] = [];
@Output() userEdit = new EventEmitter<User>();
searchQuery = '';
filteredUsers: User[] = [];
loading = false;
private destroy$ = new Subject<void>();
private searchSubject = new Subject<string>();
ngOnInit() {
this.searchSubject.pipe(
debounceTime(300),
distinctUntilChanged(),
takeUntil(this.destroy$)
).subscribe(query => this.performSearch(query));
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
onSearchInput(event: Event) {
const target = event.target as HTMLInputElement;
this.searchQuery = target.value;
this.searchSubject.next(this.searchQuery);
}
trackByUserId(index: number, user: User): number {
return user.id;
}
private async performSearch(query: string) {
if (query.length > 2) {
this.loading = true;
// Search logic
this.loading = false;
} else {
this.filteredUsers = [];
}
}
onUserEdit(user: User) {
this.userEdit.emit(user);
}
}
// Aurelia: Clean, intuitive code
export class UserSearch {
@bindable users: User[];
@bindable userEdit: (user: User) => void;
searchQuery = '';
loading = false;
// Computed properties work automatically
get filteredUsers() {
if (this.searchQuery.length < 3) return [];
return this.users.filter(user =>
user.name.toLowerCase().includes(this.searchQuery.toLowerCase())
);
}
// Simple debounced search
@watch('searchQuery')
async searchChanged(newQuery: string) {
if (newQuery.length > 2) {
this.loading = true;
// Search logic
this.loading = false;
}
}
}
<!-- Aurelia template: Clean HTML -->
<div>
<input value.bind="searchQuery & debounce:300" placeholder="Search users...">
<div if.bind="loading">Loading...</div>
<user-card repeat.for="user of filteredUsers"
user.bind="user"
user-edit.call="userEdit(user)">
</user-card>
</div>
Result: 70% less code with the same functionality and better performance.
🚀 Dependency Injection Without the Complexity
// Angular: Complex DI with decorators and modules
import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(
private http: HttpClient,
@Inject('API_URL') private apiUrl: string
) {}
}
@NgModule({
providers: [
{ provide: 'API_URL', useValue: 'https://api.example.com' },
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]
})
export class AppModule {}
// Aurelia: Simple, powerful DI
export const IUserService = DI.createInterface<IUserService>(
'IUserService',
x => x.singleton(UserService)
);
export class UserService {
private http = resolve(IHttpClient);
private config = resolve(IApiConfig);
// That's it - no modules, no complex setup
}
// Use anywhere
export class UserList {
private userService = resolve(IUserService);
// Clean, type-safe injection
}
✨ Better TypeScript Integration
// Angular: Lots of ceremony for type safety
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-detail',
template: `
<div *ngIf="user">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
<button (click)="editUser()">Edit</button>
</div>
`
})
export class UserDetailComponent {
@Input() user: User | null = null;
@Output() edit = new EventEmitter<User>();
editUser() {
if (this.user) {
this.edit.emit(this.user);
}
}
}
// Aurelia: TypeScript-first design
export class UserDetail {
@bindable user: User | null = null;
@bindable edit: (user: User) => void;
editUser() {
if (this.user) {
this.edit(this.user);
}
}
}
<!-- Aurelia template with automatic type checking -->
<div if.bind="user">
<h2>${user.name}</h2>
<p>${user.email}</p>
<button click.trigger="editUser()">Edit</button>
</div>
Your Angular Knowledge Transfers
Template Syntax Translation
[property]="value"
property.bind="value"
Same one-way binding
[(ngModel)]="value"
value.bind="value"
Simpler two-way binding
(click)="handler()"
click.trigger="handler()"
Same event handling
*ngIf="condition"
if.bind="condition"
Cleaner conditional syntax
*ngFor="let item of items"
repeat.for="item of items"
Same iteration, better performance
[class.active]="isActive"
active.class="isActive"
More intuitive class binding
Component Architecture
// Angular Component
@Component({
selector: 'app-todo-list',
template: `
<div class="todo-app">
<input
[(ngModel)]="newTodo"
(keyup.enter)="addTodo()"
placeholder="Add todo..."
>
<ul>
<li *ngFor="let todo of todos; trackBy: trackByTodoId"
[class.completed]="todo.completed">
<input
type="checkbox"
[(ngModel)]="todo.completed"
>
<span>{{ todo.text }}</span>
<button (click)="deleteTodo(todo.id)">Delete</button>
</li>
</ul>
</div>
`,
styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent {
@Input() todos: Todo[] = [];
@Output() todoAdded = new EventEmitter<Todo>();
@Output() todoDeleted = new EventEmitter<number>();
newTodo = '';
private nextId = 1;
addTodo() {
if (this.newTodo.trim()) {
const todo: Todo = {
id: this.nextId++,
text: this.newTodo.trim(),
completed: false
};
this.todoAdded.emit(todo);
this.newTodo = '';
}
}
deleteTodo(id: number) {
this.todoDeleted.emit(id);
}
trackByTodoId(index: number, todo: Todo): number {
return todo.id;
}
}
// Aurelia Component - much cleaner
export class TodoList {
@bindable todos: Todo[] = [];
@bindable todoAdded: (todo: Todo) => void;
@bindable todoDeleted: (id: number) => void;
newTodo = '';
private nextId = 1;
addTodo() {
if (this.newTodo.trim()) {
const todo: Todo = {
id: this.nextId++,
text: this.newTodo.trim(),
completed: false
};
this.todoAdded(todo);
this.newTodo = '';
}
}
deleteTodo(id: number) {
this.todoDeleted(id);
}
onEnterKey(event: KeyboardEvent) {
if (event.key === 'Enter') {
this.addTodo();
}
}
}
<!-- todo-list.html - clean, readable -->
<div class="todo-app">
<input
value.bind="newTodo"
keydown.trigger="onEnterKey($event)"
placeholder="Add todo..."
>
<ul>
<li repeat.for="todo of todos" completed.class="todo.completed">
<input type="checkbox" checked.bind="todo.completed">
<span>${todo.text}</span>
<button click.trigger="deleteTodo(todo.id)">Delete</button>
</li>
</ul>
</div>
Services and DI Comparison
// Angular Service
@Injectable({
providedIn: 'root'
})
export class DataService {
constructor(
private http: HttpClient,
@Inject('API_CONFIG') private config: ApiConfig
) {}
async getUsers(): Promise<User[]> {
return this.http.get<User[]>(`${this.config.baseUrl}/users`).toPromise();
}
}
// Aurelia Service - cleaner and more flexible
export const IDataService = DI.createInterface<IDataService>(
'IDataService',
x => x.singleton(DataService)
);
export class DataService {
private http = resolve(IHttpClient);
private config = resolve(IApiConfig);
async getUsers(): Promise<User[]> {
return this.http.get(`${this.config.baseUrl}/users`);
}
}
Migration Benefits for Angular Developers
📈 Performance Gains
No Zone.js overhead - direct DOM updates instead of change detection
Smaller bundle sizes - less framework code, better tree shaking
Faster startup - no complex bootstrap process
Better runtime performance - efficient batched updates
🧹 Development Experience Improvements
Less boilerplate - no modules, less ceremony
Simpler testing - no TestBed setup complexity
Better debugging - inspect actual DOM, not framework abstractions
Cleaner templates - HTML that looks like HTML
🚀 Modern Development Features
Built-in hot reload - better development experience
Automatic CSS loading - no need to import stylesheets
Shadow DOM support - true component encapsulation
Standards-based - closer to web platform APIs
Quick Migration Path
1. Set Up Your Aurelia Environment (5 minutes)
npx makes aurelia my-aurelia-app
cd my-aurelia-app
npm run dev
2. Convert Your First Angular Component (15 minutes)
Take any Angular component and follow this pattern:
// Angular
@Component({
selector: 'app-user-profile',
template: `
<div class="profile" [class.editing]="isEditing">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
<button *ngIf="!isEditing" (click)="startEdit()">Edit</button>
<div *ngIf="isEditing">
<input [(ngModel)]="editName" placeholder="Name">
<input [(ngModel)]="editEmail" placeholder="Email">
<button (click)="saveChanges()">Save</button>
<button (click)="cancelEdit()">Cancel</button>
</div>
</div>
`
})
export class UserProfileComponent {
@Input() user: User;
@Output() userUpdated = new EventEmitter<User>();
isEditing = false;
editName = '';
editEmail = '';
startEdit() {
this.isEditing = true;
this.editName = this.user.name;
this.editEmail = this.user.email;
}
saveChanges() {
const updatedUser = { ...this.user, name: this.editName, email: this.editEmail };
this.userUpdated.emit(updatedUser);
this.isEditing = false;
}
cancelEdit() {
this.isEditing = false;
}
}
// Aurelia - same functionality, cleaner code
export class UserProfile {
@bindable user: User;
@bindable userUpdated: (user: User) => void;
isEditing = false;
editName = '';
editEmail = '';
startEdit() {
this.isEditing = true;
this.editName = this.user.name;
this.editEmail = this.user.email;
}
saveChanges() {
const updatedUser = { ...this.user, name: this.editName, email: this.editEmail };
this.userUpdated(updatedUser);
this.isEditing = false;
}
cancelEdit() {
this.isEditing = false;
}
}
<!-- user-profile.html -->
<div class="profile" editing.class="isEditing">
<h2>${user.name}</h2>
<p>${user.email}</p>
<button if.bind="!isEditing" click.trigger="startEdit()">Edit</button>
<div if.bind="isEditing">
<input value.bind="editName" placeholder="Name">
<input value.bind="editEmail" placeholder="Email">
<button click.trigger="saveChanges()">Save</button>
<button click.trigger="cancelEdit()">Cancel</button>
</div>
</div>
3. Experience the Improvements
No change detection cycles - updates happen directly
No modules to configure - components work immediately
Better TypeScript support - everything is typed by default
Cleaner templates - HTML without framework-specific syntax
Angular vs Aurelia: Feature Comparison
TypeScript Support
Excellent
Excellent
Tie
Dependency Injection
Powerful but complex
Powerful and simple
Aurelia
Performance
Good with OnPush
Better by default
Aurelia
Learning Curve
Steep
Gentle
Aurelia
Bundle Size
Large
Smaller
Aurelia
CLI Tools
Excellent
Excellent
Tie
Enterprise Features
Comprehensive
Comprehensive
Tie
Ecosystem
Huge
Focused
Angular
Standards Compliance
Good
Excellent
Aurelia
What Angular Concepts Work in Aurelia
✅ Dependency Injection - Even more powerful and simpler ✅ TypeScript - First-class support, better integration ✅ Component Architecture - Same concepts, cleaner implementation ✅ Services - Same patterns, less boilerplate ✅ Routing - More powerful, type-safe navigation ✅ Testing - Simpler setup, same testing patterns ✅ CLI Tools - Full-featured CLI for scaffolding and building
Ready for a Better Angular Experience?
# Start your Aurelia journey
npx makes aurelia my-angular-to-aurelia-app
cd my-angular-to-aurelia-app
npm run dev
Next Steps:
Complete Getting Started Guide - Build a real app in 15 minutes
Dependency Injection Guide - Master Aurelia's DI system
Router Guide - Type-safe navigation
Testing Guide - Test your applications
Questions? Join our Discord community where developers discuss enterprise framework experiences and architectural decisions.
Ready to experience Angular without the complexity? Start building with Aurelia today.
Last updated
Was this helpful?