Form Submission
Learn how to handle form submissions with proper state management, error handling, and user feedback.
Basic Form Submission
export class ContactForm {
formData = {
name: '',
email: '',
message: ''
};
isSubmitting = false;
successMessage = '';
errorMessage = '';
async handleSubmit() {
this.isSubmitting = true;
this.errorMessage = '';
this.successMessage = '';
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.formData)
});
if (!response.ok) {
throw new Error('Submission failed');
}
this.successMessage = 'Form submitted successfully!';
this.resetForm();
} catch (error) {
this.errorMessage = 'Failed to submit form. Please try again.';
} finally {
this.isSubmitting = false;
}
}
resetForm() {
this.formData = { name: '', email: '', message: '' };
}
}<form submit.trigger="handleSubmit()">
<div class="form-group">
<label>Name</label>
<input value.bind="formData.name" required />
</div>
<div class="form-group">
<label>Email</label>
<input type="email" value.bind="formData.email" required />
</div>
<div class="form-group">
<label>Message</label>
<textarea value.bind="formData.message" required></textarea>
</div>
<div if.bind="successMessage" class="alert alert-success">
${successMessage}
</div>
<div if.bind="errorMessage" class="alert alert-error">
${errorMessage}
</div>
<button type="submit" disabled.bind="isSubmitting">
${isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>Preventing Default Submission
<!-- Method 1: Use submit.trigger (recommended) -->
<form submit.trigger="handleSubmit()">
<!-- form fields -->
</form>
<!-- Method 2: Prevent default in handler -->
<form submit.trigger="handleSubmit($event)">
<!-- form fields -->
</form>handleSubmit(event?: Event) {
event?.preventDefault();
// your logic
}Validation Before Submission
export class ValidatedForm {
formData = { name: '', email: '' };
get isValid(): boolean {
return this.formData.name.length > 0 &&
this.formData.email.includes('@');
}
handleSubmit() {
if (!this.isValid) {
alert('Please fill out all required fields');
return;
}
// Submit form
}
}<form submit.trigger="handleSubmit()">
<!-- fields -->
<button type="submit" disabled.bind="!isValid">Submit</button>
</form>Submission State Management
interface SubmissionState {
isSubmitting: boolean;
success: boolean;
error: string | null;
attempts: number;
}
export class StatefulForm {
formData = { /* ... */ };
state: SubmissionState = {
isSubmitting: false,
success: false,
error: null,
attempts: 0
};
get canSubmit(): boolean {
return !this.state.isSubmitting && this.state.attempts < 3;
}
async handleSubmit() {
if (!this.canSubmit) return;
this.state.isSubmitting = true;
this.state.error = null;
this.state.success = false;
try {
await this.submitData();
this.state.success = true;
setTimeout(() => this.resetForm(), 2000);
} catch (error) {
this.state.error = error.message;
this.state.attempts++;
} finally {
this.state.isSubmitting = false;
}
}
private async submitData() {
const response = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.formData)
});
if (!response.ok) {
throw new Error('Submission failed');
}
return response.json();
}
resetForm() {
this.formData = { /* reset */ };
this.state = {
isSubmitting: false,
success: false,
error: null,
attempts: 0
};
}
}Optimistic UI Updates
export class OptimisticForm {
items: Item[] = [];
optimisticItem: Item | null = null;
async addItem(item: Item) {
// Add optimistically
this.optimisticItem = { ...item, id: 'temp-' + Date.now() };
this.items.push(this.optimisticItem);
try {
const result = await this.saveItem(item);
// Replace optimistic item with real one
const index = this.items.indexOf(this.optimisticItem);
this.items[index] = result;
this.optimisticItem = null;
} catch (error) {
// Remove optimistic item on error
this.items = this.items.filter(i => i !== this.optimisticItem);
this.optimisticItem = null;
alert('Failed to add item');
}
}
}Debounced Auto-Save
export class AutoSaveForm {
formData = { title: '', content: '' };
saveStatus: 'saved' | 'saving' | 'unsaved' = 'saved';
saveTimer: any = null;
formDataChanged() {
this.saveStatus = 'unsaved';
// Clear existing timer
clearTimeout(this.saveTimer);
// Set new timer for auto-save
this.saveTimer = setTimeout(() => {
this.autoSave();
}, 2000);
}
async autoSave() {
this.saveStatus = 'saving';
try {
await fetch('/api/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.formData)
});
this.saveStatus = 'saved';
} catch (error) {
this.saveStatus = 'unsaved';
}
}
detaching() {
clearTimeout(this.saveTimer);
}
}<form>
<input value.bind="formData.title" input.trigger="formDataChanged()" />
<textarea value.bind="formData.content" input.trigger="formDataChanged()"></textarea>
<span class="save-status">
<span if.bind="saveStatus === 'saved'">✓ Saved</span>
<span if.bind="saveStatus === 'saving'">Saving...</span>
<span if.bind="saveStatus === 'unsaved'">Unsaved changes</span>
</span>
</form>Rate Limiting
export class RateLimitedForm {
lastSubmission: Date | null = null;
cooldownMs = 30000; // 30 seconds
get canSubmit(): boolean {
if (!this.lastSubmission) return true;
const timeSince = Date.now() - this.lastSubmission.getTime();
return timeSince > this.cooldownMs;
}
get cooldownRemaining(): number {
if (!this.lastSubmission) return 0;
const timeSince = Date.now() - this.lastSubmission.getTime();
const remaining = this.cooldownMs - timeSince;
return Math.max(0, Math.ceil(remaining / 1000));
}
async handleSubmit() {
if (!this.canSubmit) {
alert(`Please wait ${this.cooldownRemaining} seconds before submitting again`);
return;
}
// Submit form
await this.submitData();
this.lastSubmission = new Date();
}
}Multi-Step Forms
See the Template Recipes collection for complete examples, including community-contributed multi-step flows.
Best Practices
Always provide feedback - Show loading, success, and error states
Disable submit button - Prevent multiple submissions
Handle errors gracefully - Show user-friendly error messages
Validate before submitting - Client-side validation for UX
Reset form after success - Clear form or redirect user
Implement rate limiting - Prevent spam submissions
Use proper HTTP methods - POST for creation, PUT/PATCH for updates
Common Patterns
Submit on Enter Key
<form submit.trigger="handleSubmit()">
<input value.bind="query" keydown.trigger:enter="handleSubmit()" />
</form>Confirm Before Submit
handleSubmit() {
if (!confirm('Are you sure you want to submit?')) {
return;
}
// Proceed with submission
}Redirect After Success
import { resolve } from '@aurelia/kernel';
import { IRouter } from '@aurelia/router';
export class FormWithRedirect {
private readonly router = resolve(IRouter);
async handleSubmit() {
await this.submitData();
this.router.load('/success');
}
}Related
Last updated
Was this helpful?