Form inputs
Aurelia provides a powerful and intuitive approach to handling form inputs and user interactions. At the core of Aurelia's form handling is two-way binding, which creates a seamless connection between the view (HTML) and the view model (JavaScript).
Key Characteristics of Aurelia Form Inputs
Automatic Two-Way Binding: By default, form elements automatically sync changes between the view and view model
Reactive Data Flow: Input changes are immediately reflected in the view model
Minimal Boilerplate: Requires less code compared to traditional form handling approaches
Data Flow Mechanism
When a user interacts with a form input, the following process occurs:
User enters/modifies input value
Native form input events are triggered
Aurelia's binding system detects the change
View model is automatically updated
Any dependent properties or computations are instantly refreshed
Basic Form Input Binding
Text Inputs
<template>
<input type="text" value.bind="firstName">
<p>Hello, ${firstName}</p>
</template>export class MyViewModel {
firstName = '';
}Input Binding Variations
Two-Way Binding
value.bind
Default, syncs changes in both directions
One-Way Binding
value.one-way
Updates view model, but not vice versa
One-Time Binding
value.one-time
Initial value only
Textarea
<textarea value.bind="description"></textarea>Checkbox Inputs
<template>
<input type="checkbox" checked.bind="isSubscribed">
<label>Subscribe to newsletter</label>
</template>export class MyViewModel {
isSubscribed = false;
}Radio Buttons
<template>
<input type="radio" value="option1" model.bind="selectedOption">
<input type="radio" value="option2" model.bind="selectedOption">
</template>export class MyViewModel {
selectedOption = '';
}Select Dropdowns
<template>
<select value.bind="selectedCountry">
<option repeat.for="country of countries"
model.bind="country.code">
${country.name}
</option>
</select>
</template>export class MyViewModel {
countries = [
{ name: 'United States', code: 'US' },
{ name: 'Canada', code: 'CA' }
];
selectedCountry = '';
}Input Binding Considerations
Note: When using
model.bind, ensure you're binding to the entire object, not just a primitive value. This allows for more complex data binding scenarios.
Tip: Always initialize form-related properties in your view model to prevent undefined errors and provide default states.
Advanced Input Techniques
Custom Validation
Aurelia provides flexible validation through the aurelia-validation plugin. Here's a comprehensive approach to form validation:
import { inject } from 'aurelia-framework';
import { ValidationController, ValidationRules } from 'aurelia-validation';
@inject(ValidationController)
export class RegistrationViewModel {
email = '';
constructor(validationController) {
this.validationController = validationController;
ValidationRules
.ensure('email')
.required()
.email()
.on(this);
}
}<template>
<form>
<input
type="text"
value.bind="email"
error.bind="emailErrors">
<div if.bind="emailErrors" class="error">
${emailErrors}
</div>
</form>
</template>Input Formatting
Number Formatting
<template>
<input
type="text"
value.bind="price"
matcher.bind="currencyFormatter">
</template>export class ViewModel {
price = 0;
currencyFormatter = {
toView(value) {
return value
? `$${value.toFixed(2)}`
: '$0.00';
},
fromView(value) {
return parseFloat(
value.replace('$', '').replace(',', '')
);
}
};
}Conditional Binding
<template>
<input
type="text"
value.bind="username"
disabled.bind="isUsernameLocked">
<select
value.bind="accountType"
show.bind="canSelectAccountType">
<option>Basic</option>
<option>Premium</option>
</select>
</template>import { computedFrom } from 'aurelia-framework';
export class UserViewModel {
username = '';
isUsernameLocked = false;
accountType = 'Basic';
@computedFrom('username')
get canSelectAccountType() {
return this.username.length > 5;
}
}Computed Properties with Inputs
import { computedFrom } from 'aurelia-framework';
export class CalculatorViewModel {
firstNumber = 0;
secondNumber = 0;
// Computed property automatically updates
@computedFrom('firstNumber', 'secondNumber')
get sum() {
return this.firstNumber + this.secondNumber;
}
@computedFrom('firstNumber', 'secondNumber')
get average() {
return (this.firstNumber + this.secondNumber) / 2;
}
}<template>
<input type="number" value.bind="firstNumber">
<input type="number" value.bind="secondNumber">
<p>Sum: ${sum}</p>
<p>Average: ${average}</p>
</template>Validation and Error Handling
Required Fields
Ensure input is not empty
Form completeness
Email Validation
Check email format
User registration
Number Range
Validate numeric inputs
Age, quantity limits
Custom Validators
Complex validation logic
Specialized form rules
Always validate both client-side and server-side to ensure data integrity.
Form Submission
Form submission in Aurelia involves managing user input, validation, and processing data with a clean, reactive approach. The framework provides multiple strategies for handling form submissions efficiently.
Basic Form Submission
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
@inject(HttpClient)
export class RegistrationViewModel {
username = '';
email = '';
password = '';
constructor(http) {
this.http = http;
}
submitForm() {
this.http.fetch('/api/register', {
method: 'post',
body: JSON.stringify({
username: this.username,
email: this.email,
password: this.password
})
})
.then(response => response.json())
.then(data => {
// Handle successful registration
})
.catch(error => {
// Handle errors
});
}
}<template>
<form submit.trigger="submitForm()">
<input type="text" value.bind="username">
<input type="email" value.bind="email">
<input type="password" value.bind="password">
<button type="submit">Register</button>
</form>
</template>Comprehensive Form Validation
import {inject} from 'aurelia-framework';
import {ValidationController, ValidationRules} from 'aurelia-validation';
import {computedFrom} from 'aurelia-framework';
@inject(ValidationController)
export class AdvancedFormViewModel {
username = '';
email = '';
age = null;
constructor(validationController) {
this.validationController = validationController;
// Define validation rules
ValidationRules
.ensure('username')
.required()
.minLength(3)
.ensure('email')
.required()
.email()
.ensure('age')
.required()
.between(18, 99)
.on(this);
}
@computedFrom('username', 'email', 'age')
get isFormValid() {
return this.username &&
this.email &&
this.age !== null;
}
submitForm() {
this.validationController.validate()
.then(result => {
if (result.valid) {
// Process form submission
this.processRegistration();
}
});
}
processRegistration() {
// Submission logic
}
}Preventing Default Form Submission
<template>
<form submit.trigger="handleSubmit($event)">
<!-- form fields -->
</form>
</template>export class FormViewModel {
handleSubmit(event) {
// Prevent default form submission
event.preventDefault();
// Custom submission logic
this.processForm();
}
processForm() {
// Form processing
}
}Handling Form Errors
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
@inject(HttpClient)
export class ErrorHandlingViewModel {
formErrors = [];
constructor(http) {
this.http = http;
}
submitForm() {
// Reset previous errors
this.formErrors = [];
this.http.fetch('/api/submit', {
method: 'post',
body: JSON.stringify(this.formData)
})
.then(response => response.json())
.then(result => {
// Handle successful submission
})
.catch(error => {
// Populate form errors
if (error.details) {
this.formErrors = error.details.map(err => err.message);
}
});
}
}Working with Complex Forms
Complex Object Binding
import {inject} from 'aurelia-framework';
import {computedFrom} from 'aurelia-framework';
export class ComplexFormViewModel {
user = {
personal: {
firstName: '',
lastName: '',
age: null
},
contact: {
email: '',
phone: ''
},
preferences: {
newsletter: false,
interests: []
}
};
interestOptions = [
'Technology',
'Sports',
'Music',
'Travel'
];
@computedFrom('user.personal.firstName', 'user.personal.lastName')
get fullName() {
return `${this.user.personal.firstName} ${this.user.personal.lastName}`;
}
addInterest(interest) {
if (!this.user.preferences.interests.includes(interest)) {
this.user.preferences.interests.push(interest);
}
}
removeInterest(interest) {
const index = this.user.preferences.interests.indexOf(interest);
if (index > -1) {
this.user.preferences.interests.splice(index, 1);
}
}
}<template>
<form>
<!-- Personal Information -->
<section>
<input type="text" value.bind="user.personal.firstName" placeholder="First Name">
<input type="text" value.bind="user.personal.lastName" placeholder="Last Name">
<input type="number" value.bind="user.personal.age" placeholder="Age">
</section>
<!-- Contact Information -->
<section>
<input type="email" value.bind="user.contact.email" placeholder="Email">
<input type="tel" value.bind="user.contact.phone" placeholder="Phone">
</section>
<!-- Preferences -->
<section>
<label>
<input type="checkbox" checked.bind="user.preferences.newsletter">
Subscribe to Newsletter
</label>
<h3>Interests</h3>
<div>
<select value.bind="selectedInterest">
<option repeat.for="interest of interestOptions" model.bind="interest">
${interest}
</option>
</select>
<button click.delegate="addInterest(selectedInterest)">Add Interest</button>
</div>
<ul>
<li repeat.for="interest of user.preferences.interests">
${interest}
<button click.delegate="removeInterest(interest)">Remove</button>
</li>
</ul>
</section>
</form>
</template>Nested Form Structures
import {inject} from 'aurelia-framework';
import {ValidationController, ValidationRules} from 'aurelia-validation';
@inject(ValidationController)
export class NestedFormViewModel {
addresses = [{
type: 'home',
street: '',
city: '',
country: ''
}];
constructor(validationController) {
this.validationController = validationController;
// Validation for nested structures
ValidationRules
.ensure('addresses[0].street').required()
.ensure('addresses[0].city').required()
.on(this);
}
addAddress() {
this.addresses.push({
type: 'additional',
street: '',
city: '',
country: ''
});
}
removeAddress(index) {
if (this.addresses.length > 1) {
this.addresses.splice(index, 1);
}
}
}Handling Arrays of Form Data
export class OrderFormViewModel {
orderItems = [];
addOrderItem() {
this.orderItems.push({
product: '',
quantity: 1,
price: 0
});
}
removeOrderItem(index) {
this.orderItems.splice(index, 1);
}
@computedFrom('orderItems')
get totalOrderValue() {
return this.orderItems.reduce((total, item) =>
total + (item.quantity * item.price), 0);
}
}Practical Examples
User Registration Form
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
import {ValidationController, ValidationRules} from 'aurelia-validation';
@inject(HttpClient, ValidationController)
export class RegistrationViewModel {
registration = {
username: '',
email: '',
password: '',
confirmPassword: '',
agreeTOS: false
};
constructor(http, validationController) {
this.http = http;
this.validationController = validationController;
// Complex validation rules
ValidationRules
.ensure('username')
.required()
.minLength(3)
.ensure('email')
.required()
.email()
.ensure('password')
.required()
.minLength(8)
.ensure('confirmPassword')
.equals('password', 'Passwords must match')
.ensure('agreeTOS')
.equals(true, 'You must agree to Terms of Service')
.on(this.registration);
}
register() {
this.validationController.validate()
.then(result => {
if (result.valid) {
this.http.fetch('/api/register', {
method: 'POST',
body: JSON.stringify(this.registration)
});
}
});
}
}Always sanitize and validate user input, both client-side and server-side.
Last updated
Was this helpful?