Framework Comparisons

See how Aurelia's elegant syntax compares to other popular frameworks and libraries. This is just a basic comparison, but it should give you a good idea of how Aurelia compares to other frameworks and libraries.

Conditional Rendering

Conditional rendering allows you to render different content based on the state of your application.

<!-- Simple if binding -->
<div if.bind="isVisible">Hello World</div>
<!-- If/else rendering -->
<div if.bind="isAuthenticated">
 Welcome back, ${username}!
</div>
<div else>
 Please log in
</div>
{/* Simple conditional */}
{isVisible && <div>Hello World</div>}
{/* If/else rendering */}
{isAuthenticated ? (
 <div>Welcome back, {username}!</div>
) : (
 <div>Please log in</div>
)}
<!-- Simple v-if -->
<div v-if="isVisible">Hello World</div>
<!-- If/else rendering -->
<div v-if="isAuthenticated">
 Welcome back, {{ username }}!
</div>
<div v-else>
 Please log in
</div>
<!-- Simple *ngIf -->
<div *ngIf="isVisible">Hello World</div>
<!-- If/else rendering -->
<div *ngIf="isAuthenticated; else loginPrompt">
 Welcome back, {{ username }}!
</div>
<ng-template #loginPrompt>
 <div>Please log in</div>
</ng-template>
<!-- Simple conditional -->
{#if isVisible}
 <div>Hello World</div>
{/if}
<!-- If/else rendering -->
{#if isAuthenticated}
 <div>Welcome back, {username}!</div>
{:else}
 <div>Please log in</div>
{/if}

Two-way Binding

Two-way binding allows you to bind a property to an input element and have the property update when the input changes.

<!-- Two-way binding -->
<input type="text" value.bind="username">
<p>Hello, ${username}!</p>
{/* Two-way binding equivalent */}
<input
 type="text"
 value={username}
 onChange={(e) => setUsername(e.target.value)}
/>
<p>Hello, {username}!</p>
<!-- Two-way binding -->
<input type="text" v-model="username">
<p>Hello, {{ username }}!</p>
<!-- Two-way binding -->
<input type="text" [(ngModel)]="username">
<p>Hello, {{ username }}!</p>
<!-- Two-way binding -->
<input type="text" bind:value={username}>
<p>Hello, {username}!</p>

List Rendering

List rendering allows you to render a list of items.

<!-- List rendering with repeat.for -->
<ul>
 <li repeat.for="item of items">${item.name}</li>
</ul>
{/* List rendering with map */}
<ul>
 {items.map(item => (
 <li key={item.id}>{item.name}</li>
 ))}
</ul>
<!-- List rendering with v-for -->
<ul>
 <li v-for="item in items" :key="item.id">
 {{ item.name }}
 </li>
</ul>
<!-- List rendering with *ngFor -->
<ul>
 <li *ngFor="let item of items; trackBy: trackByItemId">
 {{ item.name }}
 </li>
</ul>
<!-- List rendering with each block -->
<ul>
 {#each items as item (item.id)}
 <li>{item.name}</li>
 {/each}
</ul>

Event Handling

Event handling allows you to handle events from the DOM.

<!-- Simple click event -->
<button click.trigger="handleClick()">Click me</button>
<!-- Event with parameter -->
<button click.trigger="handleClick($event)">With Event</button>
<!-- Key events -->
<input keyup.trigger="handleKeyUp($event)">
{/* Simple click event */}
<button onClick={handleClick}>Click me</button>
{/* Event with parameter */}
<button onClick={(e) => handleClick(e)}>With Event</button>
{/* Key events */}
<input onKeyUp={(e) => handleKeyUp(e)} />
<!-- Simple click event -->
<button @click="handleClick">Click me</button>
<!-- Event with parameter -->
<button @click="handleClick($event)">With Event</button>
<!-- Key events -->
<input @keyup="handleKeyUp">
<!-- Simple click event -->
<button (click)="handleClick()">Click me</button>
<!-- Event with parameter -->
<button (click)="handleClick($event)">With Event</button>
<!-- Key events -->
<input (keyup)="handleKeyUp($event)">
<!-- Simple click event -->
<button on:click={handleClick}>Click me</button>
<!-- Event with parameter -->
<button on:click={(e) => handleClick(e)}>With Event</button>
<!-- Key events -->
<input on:keyup={handleKeyUp}>

Class & Style Binding

Class and style binding allows you to bind classes and styles to an element.

<!-- Class binding -->
<div class.bind="dynamicClass">Dynamic class</div>
<div class="${condition ? 'active' : 'inactive'}">Conditional</div>
<!-- Style binding -->
<div style.bind="dynamicStyles">Dynamic styles</div>
<div style="color: ${textColor}">Dynamic color</div>
{/* Class binding */}
<div className={dynamicClass}>Dynamic class</div>
<div className={condition ? 'active' : 'inactive'}>Conditional</div>
{/* Style binding */}
<div style={dynamicStyles}>Dynamic styles</div>
<div style={{ color: textColor }}>Dynamic color</div>
<!-- Class binding -->
<div :class="dynamicClass">Dynamic class</div>
<div :class="{ active: isActive, 'text-danger': hasError }">
 Multiple classes
</div>
<div :class="[baseClass, condition ? 'active' : '']">
 Array syntax
</div>
<!-- Style binding -->
<div :style="dynamicStyles">Dynamic styles</div>
<div :style="{ color: textColor, fontSize: size + 'px' }">
 Dynamic color
</div>
<!-- Class binding -->
<div [class]="dynamicClass">Dynamic class</div>
<div [class.active]="condition">Conditional</div>
<!-- Style binding -->
<div [style]="dynamicStyles">Dynamic styles</div>
<div [style.color]="textColor">Dynamic color</div>
<!-- Class binding -->
<div class={dynamicClass}>Dynamic class</div>
<div class:active={condition}>Conditional</div>
<!-- Style binding -->
<div style={dynamicStyles}>Dynamic styles</div>
<div style:color={textColor}>Dynamic color</div>

Component Creation

Component creation allows you to create a new component.

// my-component.ts
@customElement({
 name: 'my-component',
 template: `
 <h1>\${message}</h1>
 <button click.trigger="sayHello()">Greet</button>
 `
})
export class MyComponent {
 message = 'Hello World';
 @bindable name: string;
 sayHello() {
 console.log(`Hello ${this.name}!`);
 }
}
// MyComponent.jsx
import { useState } from 'react';
function MyComponent({ name }) {
 const [message] = useState('Hello World');
 const sayHello = () => {
 console.log(`Hello ${name}!`);
 };
 return (
 <>
 <h1>{message}</h1>
 <button onClick={sayHello}>Greet</button>
 </>
 );
}
<!-- MyComponent.vue -->
<script setup lang="ts">
import { ref } from 'vue';
interface Props {
 name: string
}
const props = defineProps<Props>()
const message = ref('Hello World')
function sayHello() {
 console.log(`Hello ${props.name}!`)
}
</script>
<template>
 <h1>{{ message }}</h1>
 <button @click="sayHello">Greet</button>
</template>
// my-component.ts
@Component({
 selector: 'app-my-component',
 template: `
 <h1>{{ message }}</h1>
 <button (click)="sayHello()">Greet</button>
 `
})
export class MyComponent {
 @Input() name: string;
 message = 'Hello World';
 sayHello() {
 console.log(`Hello ${this.name}!`);
 }
}
<!-- MyComponent.svelte -->
<script>
 export let name;
 let message = 'Hello World';
 function sayHello() {
 console.log(`Hello ${name}!`);
 }
</script>
<h1>{message}</h1>
<button on:click={sayHello}>Greet</button>

Computed Properties

Computed properties are properties that are derived from other properties. They are cached and only re-evaluated when their dependencies change.

export class MyComponent {
 firstName = 'John';
 lastName = 'Doe';
 get fullName() {
 return `${this.firstName} ${this.lastName}`;
 }
}
<!-- Template -->
<div>${fullName}</div>
import { useState, useMemo } from 'react';
function MyComponent() {
 const [firstName, setFirstName] = useState('John');
 const [lastName, setLastName] = useState('Doe');
 const fullName = useMemo(() =>
 `${firstName}${lastName}`,
 [firstName, lastName]
 );
 return <div>{fullName}</div>;
}
<script setup lang="ts">
import { ref, computed } from 'vue';
const firstName = ref<string>('John')
const lastName = ref<string>('Doe')
const fullName = computed<string>(() => {
 return `${firstName.value}${lastName.value}`
})
</script>
<template>
 <div>{{ fullName }}</div>
</template>
export class MyComponent {
 firstName = 'John';
 lastName = 'Doe';
 get fullName() {
 return `${this.firstName} ${this.lastName}`;
 }
}
<!-- Template -->
<div>{{ fullName }}</div>
<script>
 let firstName = 'John';
 let lastName = 'Doe';
 $: fullName = `${firstName}${lastName}`;
</script>
<div>{fullName}</div>

Template References

Template references allow you to reference elements in the template from the component definition without needing to query the DOM.

<!-- Template reference -->
<input ref="nameInput" type="text">
<button click.trigger="focusInput()">Focus</button>
// Component class
export class MyComponent {
 nameInput: HTMLInputElement;
 focusInput() {
 this.nameInput.focus();
 }
}
import { useRef } from 'react';
function MyComponent() {
 const nameInput = useRef();
 const focusInput = () => {
 nameInput.current.focus();
 };
 return (
 <>
 <input ref={nameInput} type="text" />
 <button onClick={focusInput}>Focus</button>
 </>
 );
}
<script setup>
import { ref } from 'vue';
const nameInput = ref(null)
function focusInput() {
 nameInput.value.focus()
}
</script>
<template>
 <input ref="nameInput" type="text">
 <button @click="focusInput">Focus</button>
</template>
<!-- Template -->
<input #nameInput type="text">
<button (click)="focusInput(nameInput)">Focus</button>
// Component class
export class MyComponent {
 focusInput(input: HTMLInputElement) {
 input.focus();
 }
}
<script>
 let nameInput;
 function focusInput() {
 nameInput.focus();
 }
</script>
<input bind:this={nameInput} type="text">
<button on:click={focusInput}>Focus</button>

Lifecycle Hooks

Lifecycle hooks allow you to run code at specific stages of a component's lifecycle, such as when it mounts or unmounts.

export class MyComponent {
 data = null;
 attached() {
 // Runs when component is attached to DOM
 this.data = fetchData();
 }
 detaching() {
 // Runs before component is removed from DOM
 cleanup();
 }
}
import { useEffect } from 'react';
function MyComponent() {
 useEffect(() => {
 // Runs when component mounts
 const data = fetchData();
 return () => {
 // Cleanup when component unmounts
 cleanup();
 };
 }, []); // Empty array means run once on mount

 return <div>My Component</div>;
}
<script setup>
import { onMounted, onUnmounted } from 'vue';
onMounted(() => {
 // Runs when component is mounted
 const data = fetchData();
});
onUnmounted(() => {
 // Runs when component is unmounted
 cleanup();
});
</script>
<template>
 <div>My Component</div>
</template>
import { Component, OnInit, OnDestroy } from '@angular/core';
@Component({
 selector: 'app-my-component',
 template: '<div>My Component</div>'
})
export class MyComponent implements OnInit, OnDestroy {
 ngOnInit() {
 // Runs when component is initialized
 const data = fetchData();
 }
 ngOnDestroy() {
 // Runs when component is destroyed
 cleanup();
 }
}
<script>
 import { onMount, onDestroy } from 'svelte';
 onMount(() => {
 // Runs when component is mounted
 const data = fetchData();
 return () => {
 // Optional cleanup on unmount
 cleanup();
 };
 });
 onDestroy(() => {
 // Runs when component is destroyed
 cleanup();
 });
</script>
<div>My Component</div>

Props & Event Emission

Props allow parent components to pass data to child components, while events allow child components to communicate back to parents.

// child-component.ts
import { bindable, EventAggregator } from 'aurelia';
export class ChildComponent {
 @bindable message: string;
 @bindable onUpdate: (value: string) => void;
 handleClick() {
 // Emit event to parent
 this.onUpdate?.('Updated value');
 }
}
<!-- Parent usage -->
<child-component
 message.bind="parentMessage"
 on-update.call="handleUpdate($event)">
</child-component>
// ChildComponent.jsx
function ChildComponent({ message, onUpdate }) {
 const handleClick = () => {
 onUpdate('Updated value');
 };
 return (
 <div>
 <p>{message}</p>
 <button onClick={handleClick}>Update</button>
 </div>
 );
}
// Parent usage
<ChildComponent
 message={parentMessage}
 onUpdate={handleUpdate}
/>
<!-- ChildComponent.vue -->
<script setup>
const props = defineProps({
 message: String
});
const emit = defineEmits(['update']);
const handleClick = () => {
 emit('update', 'Updated value');
};
</script>
<template>
 <div>
 <p>{{ message }}</p>
 <button @click="handleClick">Update</button>
 </div>
</template>
<!-- Parent usage -->
<ChildComponent
 :message="parentMessage"
 @update="handleUpdate"
/>
// child.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
 selector: 'app-child',
 template: `
 <div>
 <p>{{ message }}</p>
 <button (click)="handleClick()">Update</button>
 </div>
 `
})
export class ChildComponent {
 @Input() message: string;
 @Output() update = new EventEmitter<string>();
 handleClick() {
 this.update.emit('Updated value');
 }
}
<!-- Parent usage -->
<app-child
 [message]="parentMessage"
 (update)="handleUpdate($event)">
</app-child>
<!-- ChildComponent.svelte -->
<script>
 import { createEventDispatcher } from 'svelte';
 export let message;
 const dispatch = createEventDispatcher();
 function handleClick() {
 dispatch('update', 'Updated value');
 }
</script>
<div>
 <p>{message}</p>
 <button on:click={handleClick}>Update</button>
</div>
<!-- Parent usage -->
<ChildComponent
 message={parentMessage}
 on:update={handleUpdate}
/>

Watchers & Observers

Watchers allow you to perform side effects in response to data changes.

import { watch } from 'aurelia';
export class MyComponent {
 username = '';
 @watch('username')
 usernameChanged(newValue, oldValue) {
 console.log(`Username changed from ${oldValue} to ${newValue}`);
 // Perform side effect
 validateUsername(newValue);
 }
}
import { useState, useEffect } from 'react';
function MyComponent() {
 const [username, setUsername] = useState('');
 useEffect(() => {
 // Watch username changes
 console.log(`Username changed to ${username}`);
 validateUsername(username);
 }, [username]); // Run effect when username changes

 return <input value={username} onChange={e => setUsername(e.target.value)} />;
}
<script setup>
import { ref, watch } from 'vue';
const username = ref('');
watch(username, (newValue, oldValue) => {
 console.log(`Username changed from ${oldValue} to ${newValue}`);
 validateUsername(newValue);
});
</script>
<template>
 <input v-model="username">
</template>
import { Component, OnChanges, SimpleChanges } from '@angular/core';
@Component({
 selector: 'app-my-component',
 template: '<input [(ngModel)]="username">'
})
export class MyComponent implements OnChanges {
 username = '';
 ngOnChanges(changes: SimpleChanges) {
 if (changes['username']) {
 console.log(`Username changed to ${this.username}`);
 validateUsername(this.username);
 }
 }
}
<script>
 let username = '';
 $: {
 // Reactive statement - runs when username changes
 console.log(`Username changed to ${username}`);
 validateUsername(username);
 }
</script>
<input bind:value={username}>

Slots & Content Projection

Slots allow parent components to pass content into child components, enabling flexible and reusable component designs.

// card.html
<template>
 <div class="card">
 <div class="card-header">
 <au-slot name="header">Default Header</au-slot>
 </div>
 <div class="card-body">
 <au-slot>Default content</au-slot>
 </div>
 </div>
</template>
<!-- Usage -->
<card>
 <div au-slot="header">Custom Header</div>
 <p>Custom content goes here</p>
</card>
// Card.jsx
function Card({ header, children }) {
 return (
 <div className="card">
 <div className="card-header">
 {header || 'Default Header'}
 </div>
 <div className="card-body">
 {children || 'Default content'}
 </div>
 </div>
 );
}
// Usage
<Card header={<div>Custom Header</div>}>
 <p>Custom content goes here</p>
</Card>
<!-- Card.vue -->
<template>
 <div class="card">
 <div class="card-header">
 <slot name="header">Default Header</slot>
 </div>
 <div class="card-body">
 <slot>Default content</slot>
 </div>
 </div>
</template>
<!-- Usage -->
<Card>
 <template #header>
 <div>Custom Header</div>
 </template>
 <p>Custom content goes here</p>
</Card>
// card.component.html
<div class="card">
 <div class="card-header">
 <ng-content select="[header]">Default Header</ng-content>
 </div>
 <div class="card-body">
 <ng-content>Default content</ng-content>
 </div>
</div>
<!-- Usage -->
<app-card>
 <div header>Custom Header</div>
 <p>Custom content goes here</p>
</app-card>
<!-- Card.svelte -->
<div class="card">
 <div class="card-header">
 <slot name="header">Default Header</slot>
 </div>
 <div class="card-body">
 <slot>Default content</slot>
 </div>
</div>
<!-- Usage -->
<Card>
 <div slot="header">Custom Header</div>
 <p>Custom content goes here</p>
</Card>

Forms & Validation

Form handling and validation patterns for user input.

import { newInstanceOf } from 'aurelia';
import { ValidationController, ValidationRules } from '@aurelia/validation';
export class MyForm {
 email = '';
 password = '';
 constructor(
 @newInstanceOf(ValidationController) private controller: ValidationController
 ) {
 ValidationRules
 .ensure('email').required().email()
 .ensure('password').required().minLength(8)
 .on(this);
 }
 async submit() {
 const result = await this.controller.validate();
 if (result.valid) {
 // Process form
 }
 }
}
<!-- Template -->
<form submit.trigger="submit()">
 <input type="email" value.bind="email & validate">
 <input type="password" value.bind="password & validate">
 <button type="submit">Submit</button>
</form>
import { useState } from 'react';
function MyForm() {
 const [email, setEmail] = useState('');
 const [password, setPassword] = useState('');
 const [errors, setErrors] = useState({});
 const validate = () => {
 const newErrors = {};
 if (!email) newErrors.email = 'Required';
 else if (!/\S+@\S+\.\S+/.test(email)) newErrors.email = 'Invalid email';
 if (!password) newErrors.password = 'Required';
 else if (password.length < 8) newErrors.password = 'Min 8 characters';
 return newErrors;
 };
 const handleSubmit = (e) => {
 e.preventDefault();
 const newErrors = validate();
 if (Object.keys(newErrors).length === 0) {
 // Process form
 } else {
 setErrors(newErrors);
 }
 };
 return (
 <form onSubmit={handleSubmit}>
 <input
 type="email"
 value={email}
 onChange={e => setEmail(e.target.value)}
 />
 {errors.email && <span>{errors.email}</span>}
 <input
 type="password"
 value={password}
 onChange={e => setPassword(e.target.value)}
 />
 {errors.password && <span>{errors.password}</span>}
 <button type="submit">Submit</button>
 </form>
 );
}
<script setup>
import { ref, reactive } from 'vue';
const email = ref('');
const password = ref('');
const errors = reactive({});
const validate = () => {
 const newErrors = {};
 if (!email.value) newErrors.email = 'Required';
 else if (!/\S+@\S+\.\S+/.test(email.value)) newErrors.email = 'Invalid email';
 if (!password.value) newErrors.password = 'Required';
 else if (password.value.length < 8) newErrors.password = 'Min 8 characters';
 return newErrors;
};
const handleSubmit = () => {
 const newErrors = validate();
 if (Object.keys(newErrors).length === 0) {
 // Process form
 } else {
 Object.assign(errors, newErrors);
 }
};
</script>
<template>
 <form @submit.prevent="handleSubmit">
 <input type="email" v-model="email">
 <span v-if="errors.email">{{ errors.email }}</span>
 <input type="password" v-model="password">
 <span v-if="errors.password">{{ errors.password }}</span>
 <button type="submit">Submit</button>
 </form>
</template>
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
 selector: 'app-my-form',
 template: `
 <form [formGroup]="form" (ngSubmit)="onSubmit()">
 <input type="email" formControlName="email">
 <span *ngIf="form.get('email')?.errors?.['required']">
 Required
 </span>
 <span *ngIf="form.get('email')?.errors?.['email']">
 Invalid email
 </span>
 <input type="password" formControlName="password">
 <span *ngIf="form.get('password')?.errors?.['required']">
 Required
 </span>
 <span *ngIf="form.get('password')?.errors?.['minlength']">
 Min 8 characters
 </span>
 <button type="submit" [disabled]="form.invalid">Submit</button>
 </form>
 `
})
export class MyFormComponent {
 form: FormGroup;
 constructor(private fb: FormBuilder) {
 this.form = this.fb.group({
 email: ['', [Validators.required, Validators.email]],
 password: ['', [Validators.required, Validators.minLength(8)]]
 });
 }
 onSubmit() {
 if (this.form.valid) {
 // Process form
 }
 }
}
<script>
 let email = '';
 let password = '';
 let errors = {};
 function validate() {
 const newErrors = {};
 if (!email) newErrors.email = 'Required';
 else if (!/\S+@\S+\.\S+/.test(email)) newErrors.email = 'Invalid email';
 if (!password) newErrors.password = 'Required';
 else if (password.length < 8) newErrors.password = 'Min 8 characters';
 return newErrors;
 }
 function handleSubmit() {
 const newErrors = validate();
 if (Object.keys(newErrors).length === 0) {
 // Process form
 } else {
 errors = newErrors;
 }
 }
</script>
<form on:submit|preventDefault={handleSubmit}>
 <input type="email" bind:value={email}>
 {#if errors.email}<span>{errors.email}</span>{/if}
 <input type="password" bind:value={password}>
 {#if errors.password}<span>{errors.password}</span>{/if}
 <button type="submit">Submit</button>
</form>

AltStyle によって変換されたページ (->オリジナル) /